Path.resolve, path module windows compatibility
Removes path.normalizeArray() and path.split()
This commit is contained in:
parent
48334dc0b1
commit
7c731ec9dd
@ -3,9 +3,23 @@
|
||||
This module contains utilities for dealing with file paths. Use
|
||||
`require('path')` to use it. It provides the following methods:
|
||||
|
||||
### path.normalize(p)
|
||||
|
||||
Normalize a string path, taking care of `'..'` and `'.'` parts.
|
||||
|
||||
When multiple slashes are found, they're replaces by a single one;
|
||||
when the path contains a trailing slash, it is preserved.
|
||||
On windows backslashes are used.
|
||||
|
||||
Example:
|
||||
|
||||
path.normalize('/foo/bar//baz/asdf/quux/..')
|
||||
// returns
|
||||
'/foo/bar/baz/asdf'
|
||||
|
||||
### path.join([path1], [path2], [...])
|
||||
|
||||
Join all arguments together and resolve the resulting path.
|
||||
Join all arguments together and normalize the resulting path.
|
||||
|
||||
Example:
|
||||
|
||||
@ -13,26 +27,36 @@ Example:
|
||||
... '/foo', 'bar', 'baz/asdf', 'quux', '..')
|
||||
'/foo/bar/baz/asdf'
|
||||
|
||||
### path.normalizeArray(arr)
|
||||
### path.resolve([from ...], to)
|
||||
|
||||
Normalize an array of path parts, taking care of `'..'` and `'.'` parts.
|
||||
Resolves `to` to an absolute path name and normalizes it.
|
||||
|
||||
Example:
|
||||
One ore more `from` arguments may be provided to specify the the starting
|
||||
point from where the path will be resolved. `resolve` will prepend `from`
|
||||
arguments from right to left until an absolute path is found. If no `from`
|
||||
arguments are specified, or after prepending them still no absolute path is
|
||||
found, the current working directory will be prepended eventually.
|
||||
|
||||
path.normalizeArray(['',
|
||||
'foo', 'bar', 'baz', 'asdf', 'quux', '..'])
|
||||
Trailing slashes are removed unless the path gets resolved to the root
|
||||
directory.
|
||||
|
||||
Examples:
|
||||
|
||||
path.resolve('index.html')
|
||||
// returns
|
||||
[ '', 'foo', 'bar', 'baz', 'asdf' ]
|
||||
'/home/tank/index.html'
|
||||
|
||||
### path.normalize(p)
|
||||
|
||||
Normalize a string path, taking care of `'..'` and `'.'` parts.
|
||||
|
||||
Example:
|
||||
|
||||
path.normalize('/foo/bar/baz/asdf/quux/..')
|
||||
path.resolve('/foo/bar', './baz')
|
||||
// returns
|
||||
'/foo/bar/baz/asdf'
|
||||
'/foo/baz/baz'
|
||||
|
||||
path.resolve('/foo/bar', '/tmp/file/')
|
||||
// returns
|
||||
'/tmp/file'
|
||||
|
||||
path.resolve('wwwroot', 'static_files/png/', '../gif/image.gif')
|
||||
// returns
|
||||
'/home/tank/wwwroot/static_files/gif/image.gif'
|
||||
|
||||
### path.dirname(p)
|
||||
|
||||
|
329
lib/path.js
329
lib/path.js
@ -1,127 +1,247 @@
|
||||
function validPathPart(p) {
|
||||
return typeof p === 'string' && p;
|
||||
|
||||
var isWindows = process.platform === 'win32';
|
||||
|
||||
|
||||
// resolves . and .. elements in a path array with directory names
|
||||
// there must be no slashes, empty elements, or device names (c:\) in the array
|
||||
// (so also no leading and trailing slashes - it does not distinguish relative and absolute paths)
|
||||
function normalizeArray(parts, allowAboveRoot) {
|
||||
// if the path tries to go above the root, `up` ends up > 0
|
||||
var up = 0;
|
||||
for (var i = parts.length; i >= 0; i--) {
|
||||
var last = parts[i];
|
||||
if (last == '.') {
|
||||
parts.splice(i, 1);
|
||||
} else if (last === '..') {
|
||||
parts.splice(i, 1);
|
||||
up++;
|
||||
} else if (up) {
|
||||
parts.splice(i, 1);
|
||||
up--;
|
||||
}
|
||||
}
|
||||
|
||||
// if the path is allowed to go above the root, restore leading ..s
|
||||
if (allowAboveRoot) {
|
||||
for ( ; up--; up) {
|
||||
parts.unshift('..');
|
||||
}
|
||||
}
|
||||
|
||||
return parts;
|
||||
}
|
||||
|
||||
|
||||
exports.join = function() {
|
||||
var args = Array.prototype.slice.call(arguments);
|
||||
return exports.normalizeArray(args).join('/');
|
||||
};
|
||||
if (isWindows) {
|
||||
|
||||
// Regex to split a filename into [*, dir, basename, ext]
|
||||
// windows version
|
||||
var splitPathRe = /^(.+(?:[\\\/](?!$)|:)|[\\\/])?((?:.+?)?(\.[^.]*)?)$/;
|
||||
|
||||
exports.split = function(path) {
|
||||
// split based on / and \, but only if that / is not at the start or end.
|
||||
return exports.normalizeArray(path.split(/^|[\\\/](?!$)/));
|
||||
};
|
||||
// Regex to split a windows path into three parts: [*, device, slash, tail]
|
||||
// windows-only
|
||||
var splitDeviceRe = /^([a-zA-Z]:|[\\\/]{2}[^\\\/]+[\\\/][^\\\/]+)?([\\\/])?(.*?)$/;
|
||||
|
||||
// path.resolve([from ...], to)
|
||||
// windows version
|
||||
exports.resolve = function() {
|
||||
// Prepend cwd to provided paths
|
||||
var paths = [process.cwd()].concat(Array.prototype.slice.call(arguments, 0));
|
||||
|
||||
function cleanArray(parts) {
|
||||
var i = 0;
|
||||
var l = parts.length - 1;
|
||||
var stripped = false;
|
||||
var resolvedDevice = "",
|
||||
resolvedTail = "",
|
||||
resolvedAbsolute = false;
|
||||
|
||||
// strip leading empty args
|
||||
while (i < l && !validPathPart(parts[i])) {
|
||||
stripped = true;
|
||||
i++;
|
||||
for (var i = paths.length; i >= 0; i--) {
|
||||
var path = paths[i];
|
||||
|
||||
// Skip empty and invalid entries
|
||||
if (typeof path !== 'string' || !path) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var result = splitDeviceRe.exec(path),
|
||||
device = result[1] || '',
|
||||
isUnc = device && device.charAt(1) !== ':',
|
||||
isAbsolute = !!result[2] || isUnc, // UNC paths are always absolute
|
||||
tail = result[3];
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
if (!resolvedAbsolute && resolvedDevice) {
|
||||
// If we still don't have an absolute path,
|
||||
// prepend the current path for the device found.
|
||||
|
||||
// TODO
|
||||
// Windows stores the current directories for 'other' drives
|
||||
// as hidden environment variables like =C:=c:\windows (literally)
|
||||
// var deviceCwd = os.getCwdForDrive(resolvedDevice);
|
||||
var deviceCwd = "";
|
||||
|
||||
// If there is no cwd set for the drive, it is at root
|
||||
resolvedTail = deviceCwd + '\\' + resolvedTail;
|
||||
resolvedAbsolute = true;
|
||||
}
|
||||
|
||||
// Replace slashes (in UNC share name) by backslashes
|
||||
resolvedDevice = resolvedDevice.replace(/\//g, '\\');
|
||||
|
||||
// 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)
|
||||
|
||||
// Normalize the tail path
|
||||
resolvedTail = normalizeArray(resolvedTail.split(/[\\\/]+/).filter(function(p) {
|
||||
return !!p;
|
||||
}), !resolvedAbsolute).join('\\');
|
||||
|
||||
return (resolvedDevice + (resolvedAbsolute ? '\\' : '') + resolvedTail) || '.';
|
||||
}
|
||||
|
||||
// strip tailing empty args
|
||||
while (l >= i && !validPathPart(parts[l])) {
|
||||
stripped = true;
|
||||
l--;
|
||||
// windows version
|
||||
exports.normalize = function(path) {
|
||||
var result = splitDeviceRe.exec(path),
|
||||
device = result[1] || '',
|
||||
isUnc = device && device.charAt(1) !== ':',
|
||||
isAbsolute = !!result[2] || isUnc, // UNC paths are always absolute
|
||||
tail = result[3],
|
||||
trailingSlash = /[\\\/]$/.test(tail);
|
||||
|
||||
// Normalize the tail path
|
||||
tail = normalizeArray(tail.split(/[\\\/]+/).filter(function(p) {
|
||||
return !!p;
|
||||
}), !isAbsolute).join('\\');
|
||||
|
||||
if (!tail && !isAbsolute) {
|
||||
tail = '.'
|
||||
}
|
||||
if (tail && trailingSlash) {
|
||||
tail += '\\'
|
||||
}
|
||||
|
||||
return device + (isAbsolute ? '\\' : '') + tail;
|
||||
}
|
||||
|
||||
if (stripped) {
|
||||
// if l chopped all the way back to i, then this is empty
|
||||
parts = Array.prototype.slice.call(parts, i, l + 1);
|
||||
// windows version
|
||||
exports.join = function() {
|
||||
var paths = Array.prototype.slice.call(arguments, 0).filter(function(p) {
|
||||
return p && typeof p === 'string';
|
||||
}),
|
||||
joined = paths.join('\\');
|
||||
|
||||
// Make sure that the joined path doesn't start with two slashes
|
||||
// - it will be mistaken for an unc path by normalize() -
|
||||
// unless the paths[0] also starts with two slashes
|
||||
if (/^[\\\/]{2}/.test(joined) && !/^[\\\/]{2}/.test(paths[0])) {
|
||||
joined = joined.slice(1);
|
||||
}
|
||||
|
||||
return exports.normalize(joined);
|
||||
}
|
||||
|
||||
return parts.filter(function(p) { return validPathPart(p) })
|
||||
.join('/')
|
||||
.split(/^|[\\\/](?!$)/);
|
||||
|
||||
} else /* posix */ {
|
||||
|
||||
// Regex to split a filename into [*, dir, basename, ext]
|
||||
// posix version
|
||||
var splitPathRe = /^(.+\/(?!$)|\/)?((?:.+?)?(\.[^.]*)?)$/;
|
||||
|
||||
// path.resolve([from ...], to)
|
||||
// posix version
|
||||
exports.resolve = function() {
|
||||
// Prepend cwd to provided paths
|
||||
var paths = [process.cwd()].concat(Array.prototype.slice.call(arguments, 0));
|
||||
|
||||
var resolvedPath = "",
|
||||
resolvedAbsolute = false;
|
||||
|
||||
for (var i = paths.length; i >= 0 && !resolvedAbsolute; i--) {
|
||||
var path = paths[i];
|
||||
// Skip empty and invalid entries
|
||||
if (typeof path !== 'string' || !path) {
|
||||
continue;
|
||||
}
|
||||
resolvedPath = path + '/' + resolvedPath;
|
||||
resolvedAbsolute = path.charAt(0) === '/';
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
||||
// Normalize the path
|
||||
resolvedPath = normalizeArray(resolvedPath.split('/').filter(function(p) {
|
||||
return !!p;
|
||||
}), !resolvedAbsolute).join('/');
|
||||
|
||||
return ((resolvedAbsolute ? '/' : '') + resolvedPath) || '.';
|
||||
}
|
||||
|
||||
// path.normalize(path)
|
||||
// posix version
|
||||
exports.normalize = function(path) {
|
||||
var isAbsolute = path.charAt(0) === '/',
|
||||
trailingSlash = path.slice(-1) === '/';
|
||||
|
||||
// Normalize the path
|
||||
path = normalizeArray(path.split('/').filter(function(p) {
|
||||
return !!p;
|
||||
}), !isAbsolute).join('/');
|
||||
|
||||
if (!path && !isAbsolute) {
|
||||
path = '.'
|
||||
}
|
||||
if (path && trailingSlash) {
|
||||
path += '/';
|
||||
}
|
||||
|
||||
return (isAbsolute ? '/' : '') + path;
|
||||
}
|
||||
|
||||
|
||||
// posix version
|
||||
exports.join = function() {
|
||||
var paths = Array.prototype.slice.call(arguments, 0);
|
||||
return exports.normalize(paths.filter(function(p, index) {
|
||||
return p && typeof p === 'string'
|
||||
}).join('/'));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
exports.normalizeArray = function(original) {
|
||||
var parts = cleanArray(original);
|
||||
if (!parts.length || (parts.length === 1 && !parts[0])) return ['.'];
|
||||
|
||||
// now we're fully ready to rock.
|
||||
// leading/trailing invalids have been stripped off.
|
||||
// if it comes in starting with a slash, or ending with a slash,
|
||||
var leadingSlash = (parts[0].charAt(0) === '/');
|
||||
|
||||
if (leadingSlash) parts[0] = parts[0].substr(1);
|
||||
var last = parts.slice(-1)[0];
|
||||
var tailingSlash = (last.substr(-1) === '/');
|
||||
if (tailingSlash) parts[parts.length - 1] = last.slice(0, -1);
|
||||
var directories = [];
|
||||
var prev;
|
||||
for (var i = 0, l = parts.length - 1; i <= l; i++) {
|
||||
var directory = parts[i];
|
||||
|
||||
// if it's blank, and we're not keeping blanks, then skip it.
|
||||
if (directory === '') continue;
|
||||
|
||||
// if it's a dot, then skip it
|
||||
if (directory === '.' && (directories.length ||
|
||||
(i === 0 && !(tailingSlash && i === l)) ||
|
||||
(i === 0 && leadingSlash))) continue;
|
||||
|
||||
// if we're dealing with an absolute path, then discard ..s that go
|
||||
// above that the base.
|
||||
if (leadingSlash && directories.length === 0 && directory === '..') {
|
||||
continue;
|
||||
}
|
||||
// trying to go up a dir
|
||||
if (directory === '..' && directories.length && prev !== '..' &&
|
||||
prev !== undefined) {
|
||||
directories.pop();
|
||||
prev = directories.slice(-1)[0];
|
||||
} else {
|
||||
directories.push(directory);
|
||||
prev = directory;
|
||||
}
|
||||
}
|
||||
if (!directories.length) {
|
||||
directories = [leadingSlash || tailingSlash ? '' : '.'];
|
||||
}
|
||||
var last = directories.slice(-1)[0];
|
||||
if (tailingSlash && last.substr(-1) !== '/') {
|
||||
directories[directories.length - 1] += '/';
|
||||
}
|
||||
if (leadingSlash && directories[0].charAt(0) !== '/') {
|
||||
if (directories[0] === '.') directories[0] = '';
|
||||
directories[0] = '/' + directories[0];
|
||||
}
|
||||
return directories;
|
||||
};
|
||||
|
||||
|
||||
exports.normalize = function(path) {
|
||||
return exports.join(path);
|
||||
};
|
||||
|
||||
|
||||
exports.dirname = function(path) {
|
||||
if (path.length > 1 && '\\/'.indexOf(path[path.length-1]) != -1) {
|
||||
path = path.replace(/\/+$/, '');
|
||||
}
|
||||
var lastSlash = Math.max(path.lastIndexOf('/'), path.lastIndexOf('\\'));
|
||||
switch (lastSlash) {
|
||||
case -1:
|
||||
return '.';
|
||||
case 0:
|
||||
return '/';
|
||||
default:
|
||||
return path.substring(0, lastSlash);
|
||||
var dir = splitPathRe.exec(path)[1] || '';
|
||||
if (!dir) {
|
||||
// No dirname
|
||||
return '.'
|
||||
} else if (dir.length === 1 ||
|
||||
(isWindows && dir.length <= 3 && dir.charAt(1) === ':')) {
|
||||
// It is just a slash or a drive letter with a slash
|
||||
return dir;
|
||||
} else {
|
||||
// It is a full dirname, strip trailing slash
|
||||
return dir.substring(0, dir.length - 1);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
exports.basename = function(path, ext) {
|
||||
var f = path.substr(path.lastIndexOf('/') + 1);
|
||||
var f = splitPathRe.exec(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);
|
||||
}
|
||||
@ -130,12 +250,7 @@ exports.basename = function(path, ext) {
|
||||
|
||||
|
||||
exports.extname = function(path) {
|
||||
var dot = path.lastIndexOf('.'),
|
||||
slash = Math.max(path.lastIndexOf('/'), path.lastIndexOf('\\'));
|
||||
// The last dot must be in the last path component, and it (the last dot) must
|
||||
// not start the last path component (i.e. be a dot that signifies a hidden
|
||||
// file in UNIX).
|
||||
return dot <= slash + 1 ? '' : path.substring(dot);
|
||||
return splitPathRe.exec(path)[3] || '';
|
||||
};
|
||||
|
||||
|
||||
|
@ -3,12 +3,14 @@ var assert = require('assert');
|
||||
|
||||
var path = require('path');
|
||||
|
||||
var isWindows = process.platform === 'win32';
|
||||
|
||||
var f = __filename;
|
||||
|
||||
assert.equal(path.basename(f), 'test-path.js');
|
||||
assert.equal(path.basename(f, '.js'), 'test-path');
|
||||
assert.equal(path.extname(f), '.js');
|
||||
assert.equal(path.dirname(f).substr(-11), 'test/simple');
|
||||
assert.equal(path.dirname(f).substr(-11), isWindows ? 'test\\simple' : 'test/simple');
|
||||
assert.equal(path.dirname('/a/b/'), '/a');
|
||||
assert.equal(path.dirname('/a/b'), '/a');
|
||||
assert.equal(path.dirname('/a'), '/');
|
||||
@ -87,7 +89,7 @@ var joinTests =
|
||||
];
|
||||
joinTests.forEach(function(test) {
|
||||
var actual = path.join.apply(path, test[0]);
|
||||
var expected = test[1];
|
||||
var expected = isWindows ? test[1].replace(/\//g, '\\') : test[1];
|
||||
var message = 'path.join(' + test[0].map(JSON.stringify).join(',') + ')' +
|
||||
'\n expect=' + JSON.stringify(expected) +
|
||||
'\n actual=' + JSON.stringify(actual);
|
||||
@ -96,14 +98,52 @@ joinTests.forEach(function(test) {
|
||||
});
|
||||
assert.equal(failures.length, 0, failures.join(''));
|
||||
|
||||
// path normalize tests
|
||||
if (isWindows) {
|
||||
assert.equal(path.normalize('./fixtures///b/../b/c.js'),
|
||||
'fixtures\\b\\c.js');
|
||||
assert.equal(path.normalize('/foo/../../../bar'), '\\bar');
|
||||
assert.equal(path.normalize('a//b//../b'), 'a\\b');
|
||||
assert.equal(path.normalize('a//b//./c'), 'a\\b\\c');
|
||||
assert.equal(path.normalize('a//b//.'), 'a\\b');
|
||||
} else {
|
||||
assert.equal(path.normalize('./fixtures///b/../b/c.js'),
|
||||
'fixtures/b/c.js');
|
||||
assert.equal(path.normalize('/foo/../../../bar'), '/bar');
|
||||
assert.equal(path.normalize('a//b//../b'), 'a/b');
|
||||
assert.equal(path.normalize('a//b//./c'), 'a/b/c');
|
||||
assert.equal(path.normalize('a//b//.'), 'a/b');
|
||||
}
|
||||
|
||||
assert.equal(path.normalize('./fixtures///b/../b/c.js'),
|
||||
'fixtures/b/c.js');
|
||||
assert.equal(path.normalize('/foo/../../../bar'), '/bar');
|
||||
|
||||
assert.deepEqual(path.normalizeArray(['fixtures', 'b', '', '..', 'b', 'c.js']),
|
||||
['fixtures', 'b', 'c.js']);
|
||||
|
||||
assert.equal(path.normalize('a//b//../b'), 'a/b');
|
||||
assert.equal(path.normalize('a//b//./c'), 'a/b/c');
|
||||
assert.equal(path.normalize('a//b//.'), 'a/b');
|
||||
// path.resolve tests
|
||||
if (isWindows) {
|
||||
// windows
|
||||
var resolveTests =
|
||||
// arguments result
|
||||
[[['c:/blah\\blah', 'd:/games', 'c:../a' ], 'c:\\blah\\a' ],
|
||||
[['c:/ignore', 'd:\\a/b\\c/d', '\\e.exe' ], 'd:\\e.exe' ],
|
||||
[['c:/ignore', 'c:/some/file' ], 'c:\\some\\file' ],
|
||||
[['d:/ignore', 'd:some/dir//' ], 'd:\\ignore\\some\\dir' ],
|
||||
[['.' ], process.cwd() ],
|
||||
[['//server/share', '..', 'relative\\' ], '\\\\server\\share\\relative' ]];
|
||||
} else {
|
||||
// Posix
|
||||
var resolveTests =
|
||||
// arguments result
|
||||
[[['/var/lib', '../', 'file/' ], '/var/file' ],
|
||||
[['/var/lib', '/../', 'file/' ], '/file' ],
|
||||
[['a/b/c/', '../../..' ], process.cwd() ],
|
||||
[['.' ], process.cwd() ],
|
||||
[['/some/dir', '.', '/absolute/' ], '/absolute' ]];
|
||||
}
|
||||
var failures = []
|
||||
resolveTests.forEach(function(test) {
|
||||
var actual = path.resolve.apply(path, test[0]);
|
||||
var expected = test[1];
|
||||
var message = 'path.resolve(' + test[0].map(JSON.stringify).join(',') + ')' +
|
||||
'\n expect=' + JSON.stringify(expected) +
|
||||
'\n actual=' + JSON.stringify(actual);
|
||||
if (actual !== expected) failures.push('\n' + message);
|
||||
// assert.equal(actual, expected, message);
|
||||
});
|
||||
assert.equal(failures.length, 0, failures.join(''));
|
||||
|
Loading…
x
Reference in New Issue
Block a user