Browse Source

Path.resolve, path module windows compatibility

Removes path.normalizeArray() and path.split()
v0.7.4-release
Bert Belder 14 years ago
committed by Ryan Dahl
parent
commit
7c731ec9dd
  1. 50
      doc/api/path.markdown
  2. 299
      lib/path.js
  3. 62
      test/simple/test-path.js

50
doc/api/path.markdown

@ -3,9 +3,23 @@
This module contains utilities for dealing with file paths. Use This module contains utilities for dealing with file paths. Use
`require('path')` to use it. It provides the following methods: `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], [...]) ### path.join([path1], [path2], [...])
Join all arguments together and resolve the resulting path. Join all arguments together and normalize the resulting path.
Example: Example:
@ -13,26 +27,36 @@ Example:
... '/foo', 'bar', 'baz/asdf', 'quux', '..') ... '/foo', 'bar', 'baz/asdf', 'quux', '..')
'/foo/bar/baz/asdf' '/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(['', Trailing slashes are removed unless the path gets resolved to the root
'foo', 'bar', 'baz', 'asdf', 'quux', '..']) directory.
// returns
[ '', 'foo', 'bar', 'baz', 'asdf' ]
### path.normalize(p) Examples:
Normalize a string path, taking care of `'..'` and `'.'` parts. path.resolve('index.html')
// returns
'/home/tank/index.html'
Example: path.resolve('/foo/bar', './baz')
// returns
'/foo/baz/baz'
path.normalize('/foo/bar/baz/asdf/quux/..') path.resolve('/foo/bar', '/tmp/file/')
// returns // returns
'/foo/bar/baz/asdf' '/tmp/file'
path.resolve('wwwroot', 'static_files/png/', '../gif/image.gif')
// returns
'/home/tank/wwwroot/static_files/gif/image.gif'
### path.dirname(p) ### path.dirname(p)

299
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() { if (isWindows) {
var args = Array.prototype.slice.call(arguments);
return exports.normalizeArray(args).join('/');
};
// Regex to split a filename into [*, dir, basename, ext]
// windows version
var splitPathRe = /^(.+(?:[\\\/](?!$)|:)|[\\\/])?((?:.+?)?(\.[^.]*)?)$/;
exports.split = function(path) { // Regex to split a windows path into three parts: [*, device, slash, tail]
// split based on / and \, but only if that / is not at the start or end. // windows-only
return exports.normalizeArray(path.split(/^|[\\\/](?!$)/)); 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));
var resolvedDevice = "",
resolvedTail = "",
resolvedAbsolute = false;
for (var i = paths.length; i >= 0; i--) {
var path = paths[i];
function cleanArray(parts) { // Skip empty and invalid entries
var i = 0; if (typeof path !== 'string' || !path) {
var l = parts.length - 1; continue;
var stripped = false; }
// strip leading empty args var result = splitDeviceRe.exec(path),
while (i < l && !validPathPart(parts[i])) { device = result[1] || '',
stripped = true; isUnc = device && device.charAt(1) !== ':',
i++; 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;
} }
// strip tailing empty args if (!resolvedDevice) {
while (l >= i && !validPathPart(parts[l])) { resolvedDevice = device;
stripped = true; }
l--; if (!resolvedAbsolute) {
resolvedTail = tail + '\\' + resolvedTail;
resolvedAbsolute = isAbsolute;
} }
if (stripped) { if (resolvedDevice && resolvedAbsolute) {
// if l chopped all the way back to i, then this is empty break;
parts = Array.prototype.slice.call(parts, i, l + 1); }
} }
return parts.filter(function(p) { return validPathPart(p) }) if (!resolvedAbsolute && resolvedDevice) {
.join('/') // If we still don't have an absolute path,
.split(/^|[\\\/](?!$)/); // 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) || '.';
}
// 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;
}
// 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);
}
exports.normalizeArray = function(original) {
var parts = cleanArray(original);
if (!parts.length || (parts.length === 1 && !parts[0])) return ['.'];
// now we're fully ready to rock. } else /* posix */ {
// 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); // Regex to split a filename into [*, dir, basename, ext]
var last = parts.slice(-1)[0]; // posix version
var tailingSlash = (last.substr(-1) === '/'); var splitPathRe = /^(.+\/(?!$)|\/)?((?:.+?)?(\.[^.]*)?)$/;
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. // path.resolve([from ...], to)
if (directory === '') continue; // posix version
exports.resolve = function() {
// Prepend cwd to provided paths
var paths = [process.cwd()].concat(Array.prototype.slice.call(arguments, 0));
// if it's a dot, then skip it var resolvedPath = "",
if (directory === '.' && (directories.length || resolvedAbsolute = false;
(i === 0 && !(tailingSlash && i === l)) ||
(i === 0 && leadingSlash))) continue;
// if we're dealing with an absolute path, then discard ..s that go for (var i = paths.length; i >= 0 && !resolvedAbsolute; i--) {
// above that the base. var path = paths[i];
if (leadingSlash && directories.length === 0 && directory === '..') { // Skip empty and invalid entries
if (typeof path !== 'string' || !path) {
continue; continue;
} }
// trying to go up a dir resolvedPath = path + '/' + resolvedPath;
if (directory === '..' && directories.length && prev !== '..' && resolvedAbsolute = path.charAt(0) === '/';
prev !== undefined) {
directories.pop();
prev = directories.slice(-1)[0];
} else {
directories.push(directory);
prev = directory;
} }
// 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) || '.';
} }
if (!directories.length) {
directories = [leadingSlash || tailingSlash ? '' : '.']; // 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 = '.'
} }
var last = directories.slice(-1)[0]; if (path && trailingSlash) {
if (tailingSlash && last.substr(-1) !== '/') { path += '/';
directories[directories.length - 1] += '/';
} }
if (leadingSlash && directories[0].charAt(0) !== '/') {
if (directories[0] === '.') directories[0] = ''; return (isAbsolute ? '/' : '') + path;
directories[0] = '/' + directories[0];
} }
return directories;
};
exports.normalize = function(path) { // posix version
return exports.join(path); 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.dirname = function(path) { exports.dirname = function(path) {
if (path.length > 1 && '\\/'.indexOf(path[path.length-1]) != -1) { var dir = splitPathRe.exec(path)[1] || '';
path = path.replace(/\/+$/, ''); if (!dir) {
} // No dirname
var lastSlash = Math.max(path.lastIndexOf('/'), path.lastIndexOf('\\')); return '.'
switch (lastSlash) { } else if (dir.length === 1 ||
case -1: (isWindows && dir.length <= 3 && dir.charAt(1) === ':')) {
return '.'; // It is just a slash or a drive letter with a slash
case 0: return dir;
return '/'; } else {
default: // It is a full dirname, strip trailing slash
return path.substring(0, lastSlash); return dir.substring(0, dir.length - 1);
} }
}; };
exports.basename = function(path, ext) { 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) { if (ext && f.substr(-1 * ext.length) === ext) {
f = f.substr(0, f.length - ext.length); f = f.substr(0, f.length - ext.length);
} }
@ -130,12 +250,7 @@ exports.basename = function(path, ext) {
exports.extname = function(path) { exports.extname = function(path) {
var dot = path.lastIndexOf('.'), return splitPathRe.exec(path)[3] || '';
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);
}; };

62
test/simple/test-path.js

@ -3,12 +3,14 @@ var assert = require('assert');
var path = require('path'); var path = require('path');
var isWindows = process.platform === 'win32';
var f = __filename; var f = __filename;
assert.equal(path.basename(f), 'test-path.js'); assert.equal(path.basename(f), 'test-path.js');
assert.equal(path.basename(f, '.js'), 'test-path'); assert.equal(path.basename(f, '.js'), 'test-path');
assert.equal(path.extname(f), '.js'); 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/b'), '/a'); assert.equal(path.dirname('/a/b'), '/a');
assert.equal(path.dirname('/a'), '/'); assert.equal(path.dirname('/a'), '/');
@ -87,7 +89,7 @@ var joinTests =
]; ];
joinTests.forEach(function(test) { joinTests.forEach(function(test) {
var actual = path.join.apply(path, test[0]); 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(',') + ')' + var message = 'path.join(' + test[0].map(JSON.stringify).join(',') + ')' +
'\n expect=' + JSON.stringify(expected) + '\n expect=' + JSON.stringify(expected) +
'\n actual=' + JSON.stringify(actual); '\n actual=' + JSON.stringify(actual);
@ -96,14 +98,52 @@ joinTests.forEach(function(test) {
}); });
assert.equal(failures.length, 0, failures.join('')); assert.equal(failures.length, 0, failures.join(''));
// path normalize tests
assert.equal(path.normalize('./fixtures///b/../b/c.js'), 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'); 'fixtures/b/c.js');
assert.equal(path.normalize('/foo/../../../bar'), '/bar'); assert.equal(path.normalize('/foo/../../../bar'), '/bar');
assert.equal(path.normalize('a//b//../b'), 'a/b');
assert.deepEqual(path.normalizeArray(['fixtures', 'b', '', '..', 'b', 'c.js']), assert.equal(path.normalize('a//b//./c'), 'a/b/c');
['fixtures', 'b', 'c.js']); assert.equal(path.normalize('a//b//.'), 'a/b');
}
assert.equal(path.normalize('a//b//../b'), 'a/b'); // path.resolve tests
assert.equal(path.normalize('a//b//./c'), 'a/b/c'); if (isWindows) {
assert.equal(path.normalize('a//b//.'), 'a/b'); // 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…
Cancel
Save