0
0
mirror of https://github.com/nodejs/node.git synced 2024-11-30 23:43:09 +01:00
nodejs/lib/path.js

603 lines
14 KiB
JavaScript
Raw Normal View History

'use strict';
const util = require('util');
const isWindows = process.platform === 'win32';
function assertPath(path) {
if (typeof path !== 'string') {
throw new TypeError('Path must be a string. Received ' +
util.inspect(path));
}
}
2011-01-07 01:06:27 +01:00
// resolves . and .. elements in a path array with directory names there
// must be no slashes or device names (c:\) in the array
2011-01-07 01:06:27 +01:00
// (so also no leading and trailing slashes - it does not distinguish
// relative and absolute paths)
function normalizeArray(parts, allowAboveRoot) {
var res = [];
for (var i = 0; i < parts.length; i++) {
var p = parts[i];
// ignore empty parts
if (!p || p === '.')
continue;
if (p === '..') {
if (res.length && res[res.length - 1] !== '..') {
res.pop();
} else if (allowAboveRoot) {
res.push('..');
}
} else {
res.push(p);
}
}
return res;
}
// Returns an array with empty elements removed from either end of the input
// array or the original array if no elements need to be removed
function trimArray(arr) {
var lastIndex = arr.length - 1;
var start = 0;
for (; start <= lastIndex; start++) {
if (arr[start])
break;
}
var end = lastIndex;
for (; end >= 0; end--) {
if (arr[end])
break;
}
if (start === 0 && end === lastIndex)
return arr;
if (start > end)
return [];
return arr.slice(start, end + 1);
}
// Regex to split a windows path into three parts: [*, device, slash,
// tail] windows-only
const splitDeviceRe =
/^([a-zA-Z]:|[\\\/]{2}[^\\\/]+[\\\/]+[^\\\/]+)?([\\\/])?([\s\S]*?)$/;
// Regex to split the tail part of the above into [*, dir, basename, ext]
const splitTailRe =
/^([\s\S]*?)((?:\.{1,2}|[^\\\/]+?|)(\.[^.\/\\]*|))(?:[\\\/]*)$/;
var win32 = {};
// Function to split a filename into [root, dir, basename, ext]
function win32SplitPath(filename) {
// Separate device+slash from tail
const result = splitDeviceRe.exec(filename);
const device = (result[1] || '') + (result[2] || '');
const tail = result[3];
// Split the tail into dir, basename and extension
const result2 = splitTailRe.exec(tail);
const dir = result2[1];
const basename = result2[2];
const ext = result2[3];
return [device, dir, basename, ext];
}
2010-11-21 23:13:54 +01:00
function win32StatPath(path) {
const result = splitDeviceRe.exec(path);
const device = result[1] || '';
const isUnc = !!device && device[1] !== ':';
return {
device,
isUnc,
isAbsolute: isUnc || !!result[2], // UNC paths are always absolute
tail: result[3]
};
}
function normalizeUNCRoot(device) {
return '\\\\' + device.replace(/^[\\\/]+/, '').replace(/[\\\/]+/g, '\\');
}
2010-11-21 23:13:54 +01:00
// path.resolve([from ...], to)
win32.resolve = function() {
var resolvedDevice = '';
var resolvedTail = '';
var resolvedAbsolute = false;
for (var i = arguments.length - 1; i >= -1; i--) {
var path;
if (i >= 0) {
path = arguments[i];
} else if (!resolvedDevice) {
path = process.cwd();
} else {
// Windows has the concept of drive-specific current working
// directories. If we've resolved a drive letter but not yet an
// absolute path, get cwd for that drive. We're sure the device is not
// an unc path at this points, because unc paths are always absolute.
path = process.env['=' + resolvedDevice];
// Verify that a drive-local cwd was found and that it actually points
// to our drive. If not, default to the drive's root.
if (!path || path.substr(0, 3).toLowerCase() !==
resolvedDevice.toLowerCase() + '\\') {
path = resolvedDevice + '\\';
}
}
2010-11-21 23:13:54 +01:00
assertPath(path);
// Skip empty entries
if (path === '') {
continue;
}
const result = win32StatPath(path);
const device = result.device;
var isUnc = result.isUnc;
const isAbsolute = result.isAbsolute;
const tail = result.tail;
if (device &&
resolvedDevice &&
device.toLowerCase() !== resolvedDevice.toLowerCase()) {
// This path points to another device so it is not applicable
continue;
}
if (!resolvedDevice) {
resolvedDevice = device;
}
if (!resolvedAbsolute) {
resolvedTail = tail + '\\' + resolvedTail;
resolvedAbsolute = isAbsolute;
}
if (resolvedDevice && resolvedAbsolute) {
break;
}
}
// Convert slashes to backslashes when `resolvedDevice` points to an UNC
// root. Also squash multiple slashes into a single one where appropriate.
if (isUnc) {
resolvedDevice = normalizeUNCRoot(resolvedDevice);
}
// At this point the path should be resolved to a full absolute path,
// but handle relative paths to be safe (might happen when process.cwd()
// fails)
2011-01-07 01:06:27 +01:00
// Normalize the tail path
resolvedTail = normalizeArray(resolvedTail.split(/[\\\/]+/),
!resolvedAbsolute).join('\\');
return (resolvedDevice + (resolvedAbsolute ? '\\' : '') + resolvedTail) ||
'.';
};
2010-11-21 23:13:54 +01:00
win32.normalize = function(path) {
assertPath(path);
const result = win32StatPath(path);
var device = result.device;
const isUnc = result.isUnc;
const isAbsolute = result.isAbsolute;
var tail = result.tail;
const trailingSlash = /[\\\/]$/.test(tail);
2010-11-21 23:13:54 +01:00
// Normalize the tail path
tail = normalizeArray(tail.split(/[\\\/]+/), !isAbsolute).join('\\');
if (!tail && !isAbsolute) {
tail = '.';
}
if (tail && trailingSlash) {
tail += '\\';
}
2011-01-07 01:06:27 +01:00
// Convert slashes to backslashes when `device` points to an UNC root.
// Also squash multiple slashes into a single one where appropriate.
if (isUnc) {
device = normalizeUNCRoot(device);
}
return device + (isAbsolute ? '\\' : '') + tail;
};
2011-03-06 03:42:33 +01:00
win32.isAbsolute = function(path) {
assertPath(path);
return win32StatPath(path).isAbsolute;
};
win32.join = function() {
var paths = [];
for (var i = 0; i < arguments.length; i++) {
var arg = arguments[i];
assertPath(arg);
if (arg) {
paths.push(arg);
}
}
var joined = paths.join('\\');
// Make sure that the joined path doesn't start with two slashes, because
// normalize() will mistake it for an UNC path then.
//
// This step is skipped when it is very clear that the user actually
// intended to point at an UNC path. This is assumed when the first
// non-empty string arguments starts with exactly two slashes followed by
// at least one more non-slash character.
//
// Note that for normalize() to treat a path as an UNC path it needs to
// have at least 2 components, so we don't filter for that here.
// This means that the user can use join to construct UNC paths from
// a server name and a share name; for example:
// path.join('//server', 'share') -> '\\\\server\\share\')
if (!/^[\\\/]{2}[^\\\/]/.test(paths[0])) {
joined = joined.replace(/^[\\\/]{2,}/, '\\');
}
return win32.normalize(joined);
};
// path.relative(from, to)
// it will solve the relative path from 'from' to 'to', for instance:
// from = 'C:\\orandea\\test\\aaa'
// to = 'C:\\orandea\\impl\\bbb'
// The output of the function should be: '..\\..\\impl\\bbb'
win32.relative = function(from, to) {
assertPath(from);
assertPath(to);
from = win32.resolve(from);
to = win32.resolve(to);
// windows is not case sensitive
var lowerFrom = from.toLowerCase();
var lowerTo = to.toLowerCase();
var toParts = trimArray(to.split('\\'));
var lowerFromParts = trimArray(lowerFrom.split('\\'));
var lowerToParts = trimArray(lowerTo.split('\\'));
2011-03-06 03:42:33 +01:00
var length = Math.min(lowerFromParts.length, lowerToParts.length);
var samePartsLength = length;
for (var i = 0; i < length; i++) {
if (lowerFromParts[i] !== lowerToParts[i]) {
samePartsLength = i;
break;
2011-03-06 03:42:33 +01:00
}
}
2011-03-06 03:42:33 +01:00
if (samePartsLength === 0) {
return to;
}
2011-03-06 03:42:33 +01:00
var outputParts = [];
for (var j = samePartsLength; j < lowerFromParts.length; j++) {
outputParts.push('..');
}
2011-03-06 03:42:33 +01:00
outputParts = outputParts.concat(toParts.slice(samePartsLength));
2011-03-06 03:42:33 +01:00
return outputParts.join('\\');
};
2011-03-06 03:42:33 +01:00
win32._makeLong = function(path) {
// Note: this will *probably* throw somewhere.
if (typeof path !== 'string')
return path;
2011-03-06 03:42:33 +01:00
if (!path) {
return '';
}
2011-03-06 03:42:33 +01:00
var resolvedPath = win32.resolve(path);
if (/^[a-zA-Z]\:\\/.test(resolvedPath)) {
// path is local filesystem path, which needs to be converted
// to long UNC path.
return '\\\\?\\' + resolvedPath;
} else if (/^\\\\[^?.]/.test(resolvedPath)) {
// path is network UNC path, which needs to be converted
// to long UNC path.
return '\\\\?\\UNC\\' + resolvedPath.substring(2);
}
return path;
};
win32.dirname = function(path) {
const result = win32SplitPath(path);
const root = result[0];
var dir = result[1];
if (!root && !dir) {
// No dirname whatsoever
return '.';
}
if (dir) {
// It has a dirname, strip trailing slash
dir = dir.substr(0, dir.length - 1);
}
return root + dir;
};
win32.basename = function(path, ext) {
if (ext !== undefined && typeof ext !== 'string')
throw new TypeError('"ext" argument must be a string');
var f = win32SplitPath(path)[2];
// TODO: make this comparison case-insensitive on windows?
if (ext && f.substr(-1 * ext.length) === ext) {
f = f.substr(0, f.length - ext.length);
}
return f;
};
win32.extname = function(path) {
return win32SplitPath(path)[3];
};
2010-04-21 03:17:54 +02:00
win32.format = function(pathObject) {
if (pathObject === null || typeof pathObject !== 'object') {
throw new TypeError(
'Parameter "pathObject" must be an object, not ' + typeof pathObject
);
}
var dir = pathObject.dir || pathObject.root;
var base = pathObject.base ||
((pathObject.name || '') + (pathObject.ext || ''));
if (!dir) {
return base;
}
if (dir === pathObject.root) {
return dir + base;
}
return dir + win32.sep + base;
};
win32.parse = function(pathString) {
assertPath(pathString);
var allParts = win32SplitPath(pathString);
return {
root: allParts[0],
dir: allParts[0] + allParts[1].slice(0, -1),
base: allParts[2],
ext: allParts[3],
name: allParts[2].slice(0, allParts[2].length - allParts[3].length)
};
};
2010-11-21 23:13:54 +01:00
win32.sep = '\\';
win32.delimiter = ';';
// Split a filename into [root, dir, basename, ext], unix version
// 'root' is just a slash, or nothing.
const splitPathRe =
/^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/;
var posix = {};
function posixSplitPath(filename) {
const out = splitPathRe.exec(filename);
out.shift();
return out;
}
// path.resolve([from ...], to)
// posix version
posix.resolve = function() {
var resolvedPath = '';
var resolvedAbsolute = false;
for (var i = arguments.length - 1; i >= -1 && !resolvedAbsolute; i--) {
var path = (i >= 0) ? arguments[i] : process.cwd();
assertPath(path);
// Skip empty entries
if (path === '') {
continue;
}
2011-03-06 03:42:33 +01:00
resolvedPath = path + '/' + resolvedPath;
resolvedAbsolute = path[0] === '/';
}
2011-03-06 03:42:33 +01:00
// At this point the path should be resolved to a full absolute path, but
// handle relative paths to be safe (might happen when process.cwd() fails)
2011-03-06 03:42:33 +01:00
// Normalize the path
resolvedPath = normalizeArray(resolvedPath.split('/'),
!resolvedAbsolute).join('/');
2011-03-06 03:42:33 +01:00
return ((resolvedAbsolute ? '/' : '') + resolvedPath) || '.';
};
2011-03-06 03:42:33 +01:00
// path.normalize(path)
// posix version
posix.normalize = function(path) {
assertPath(path);
const isAbsolute = posix.isAbsolute(path);
const trailingSlash = path && path[path.length - 1] === '/';
// Normalize the path
path = normalizeArray(path.split('/'), !isAbsolute).join('/');
2011-03-06 03:42:33 +01:00
if (!path && !isAbsolute) {
path = '.';
}
if (path && trailingSlash) {
path += '/';
}
return (isAbsolute ? '/' : '') + path;
};
// posix version
posix.isAbsolute = function(path) {
assertPath(path);
return !!path && path[0] === '/';
};
2011-03-06 03:42:33 +01:00
// posix version
posix.join = function() {
var path = '';
for (var i = 0; i < arguments.length; i++) {
var segment = arguments[i];
assertPath(segment);
if (segment) {
if (!path) {
path += segment;
} else {
path += '/' + segment;
2011-03-06 03:42:33 +01:00
}
}
}
return posix.normalize(path);
};
2011-03-06 03:42:33 +01:00
// path.relative(from, to)
// posix version
posix.relative = function(from, to) {
assertPath(from);
assertPath(to);
from = posix.resolve(from).substr(1);
to = posix.resolve(to).substr(1);
var fromParts = trimArray(from.split('/'));
var toParts = trimArray(to.split('/'));
2011-03-06 03:42:33 +01:00
var length = Math.min(fromParts.length, toParts.length);
var samePartsLength = length;
for (var i = 0; i < length; i++) {
if (fromParts[i] !== toParts[i]) {
samePartsLength = i;
break;
}
}
var outputParts = [];
for (var j = samePartsLength; j < fromParts.length; j++) {
outputParts.push('..');
}
outputParts = outputParts.concat(toParts.slice(samePartsLength));
2010-04-21 03:17:54 +02:00
return outputParts.join('/');
};
posix._makeLong = function(path) {
return path;
};
posix.dirname = function(path) {
const result = posixSplitPath(path);
const root = result[0];
var dir = result[1];
if (!root && !dir) {
// No dirname whatsoever
2011-01-07 01:06:27 +01:00
return '.';
}
if (dir) {
// It has a dirname, strip trailing slash
2012-05-20 21:26:29 +02:00
dir = dir.substr(0, dir.length - 1);
}
return root + dir;
2010-04-21 03:17:54 +02:00
};
2010-11-21 23:13:54 +01:00
posix.basename = function(path, ext) {
if (ext !== undefined && typeof ext !== 'string')
throw new TypeError('"ext" argument must be a string');
var f = posixSplitPath(path)[2];
2010-04-21 03:17:54 +02:00
if (ext && f.substr(-1 * ext.length) === ext) {
f = f.substr(0, f.length - ext.length);
}
return f;
};
2010-11-21 23:13:54 +01:00
posix.extname = function(path) {
return posixSplitPath(path)[3];
2010-04-21 03:17:54 +02:00
};
2010-11-21 23:13:54 +01:00
posix.format = function(pathObject) {
if (pathObject === null || typeof pathObject !== 'object') {
throw new TypeError(
'Parameter "pathObject" must be an object, not ' + typeof pathObject
);
}
2010-06-16 15:51:19 +02:00
var dir = pathObject.dir || pathObject.root;
var base = pathObject.base ||
((pathObject.name || '') + (pathObject.ext || ''));
if (!dir) {
return base;
}
if (dir === pathObject.root) {
return dir + base;
}
return dir + posix.sep + base;
};
2011-11-25 09:29:06 +01:00
posix.parse = function(pathString) {
assertPath(pathString);
var allParts = posixSplitPath(pathString);
return {
root: allParts[0],
dir: allParts[0] + allParts[1].slice(0, -1),
base: allParts[2],
ext: allParts[3],
name: allParts[2].slice(0, allParts[2].length - allParts[3].length)
};
2010-04-21 03:17:54 +02:00
};
2011-11-25 09:29:06 +01:00
posix.sep = '/';
posix.delimiter = ':';
2011-11-25 09:29:06 +01:00
if (isWindows)
module.exports = win32;
else /* posix */
module.exports = posix;
2011-11-25 09:29:06 +01:00
module.exports.posix = posix;
module.exports.win32 = win32;