Implement new path.join behavior
1. Express desired path.join behavior in tests. 2. Update fs.realpath to reflect new path.join behavior 3. Update url.resolve() to use new path.join behavior.
This commit is contained in:
parent
25eecd179b
commit
9996b459e1
48
lib/fs.js
48
lib/fs.js
@ -488,8 +488,8 @@ function realpathSync (p) {
|
||||
if (p.charAt(0) !== '/') {
|
||||
p = path.join(process.cwd(), p);
|
||||
}
|
||||
p = p.split('/');
|
||||
var buf = [ '' ];
|
||||
p = path.split(p);
|
||||
var buf = [];
|
||||
var seenLinks = {};
|
||||
var knownHard = {};
|
||||
// walk down the path, swapping out linked pathparts for their real
|
||||
@ -499,7 +499,7 @@ function realpathSync (p) {
|
||||
for (var i = 0; i < p.length; i ++) {
|
||||
// skip over empty path parts.
|
||||
if (p[i] === '') continue;
|
||||
var part = buf.join('/')+'/'+p[i];
|
||||
var part = path.join.apply(path, buf.concat(p[i]));
|
||||
if (knownHard[part]) {
|
||||
buf.push( p[i] );
|
||||
continue;
|
||||
@ -519,19 +519,22 @@ function realpathSync (p) {
|
||||
var target = seenLinks[id];
|
||||
if (target.charAt(0) === '/') {
|
||||
// absolute. Start over.
|
||||
buf = [''];
|
||||
p = path.normalizeArray(target.split('/').concat(p.slice(i + 1)));
|
||||
i = 0;
|
||||
buf = [];
|
||||
p = path.normalizeArray(path.split(target).concat(p.slice(i + 1)));
|
||||
i = -1;
|
||||
continue;
|
||||
}
|
||||
// not absolute. join and splice.
|
||||
target = target.split('/');
|
||||
if (i === 0 && p[i].charAt(0) === "/") {
|
||||
target = "/"+target;
|
||||
}
|
||||
target = path.split(target);
|
||||
Array.prototype.splice.apply(p, [i, 1].concat(target));
|
||||
p = path.normalizeArray(p);
|
||||
i = 0;
|
||||
buf = [''];
|
||||
i = -1;
|
||||
buf = [];
|
||||
}
|
||||
return buf.join('/') || '/';
|
||||
return path.join(buf.join('/') || '/');
|
||||
}
|
||||
|
||||
|
||||
@ -539,15 +542,15 @@ function realpath (p, cb) {
|
||||
if (p.charAt(0) !== '/') {
|
||||
p = path.join(process.cwd(), p);
|
||||
}
|
||||
p = p.split('/');
|
||||
var buf = [ '' ];
|
||||
p = path.split(p);
|
||||
var buf = [];
|
||||
var seenLinks = {};
|
||||
var knownHard = {};
|
||||
// walk down the path, swapping out linked pathparts for their real
|
||||
// values, and pushing non-link path bits onto the buffer.
|
||||
// then return the buffer.
|
||||
// NB: path.length changes.
|
||||
var i = 0;
|
||||
var i = -1;
|
||||
var part;
|
||||
LOOP();
|
||||
function LOOP () {
|
||||
@ -555,7 +558,7 @@ function realpath (p, cb) {
|
||||
if (!(i < p.length)) return exit();
|
||||
// skip over empty path parts.
|
||||
if (p[i] === '') return process.nextTick(LOOP);
|
||||
part = buf.join('/')+'/'+p[i];
|
||||
part = path.join(buf.join('/')+'/'+p[i]);
|
||||
if (knownHard[part]) {
|
||||
buf.push( p[i] );
|
||||
return process.nextTick(LOOP);
|
||||
@ -583,21 +586,24 @@ function realpath (p, cb) {
|
||||
if (er) return cb(er);
|
||||
if (target.charAt(0) === '/') {
|
||||
// absolute. Start over.
|
||||
buf = [''];
|
||||
p = path.normalizeArray(target.split('/').concat(p.slice(i + 1)));
|
||||
i = 0;
|
||||
buf = [];
|
||||
p = path.normalizeArray(path.split(target).concat(p.slice(i + 1)));
|
||||
i = -1;
|
||||
return process.nextTick(LOOP);
|
||||
}
|
||||
// not absolute. join and splice.
|
||||
target = target.split('/');
|
||||
if (i === 0 && p[i].charAt(0) === "/") {
|
||||
target = "/"+target;
|
||||
}
|
||||
target = path.split(target);
|
||||
Array.prototype.splice.apply(p, [i, 1].concat(target));
|
||||
p = path.normalizeArray(p);
|
||||
i = 0;
|
||||
buf = [''];
|
||||
i = -1;
|
||||
buf = [];
|
||||
return process.nextTick(LOOP);
|
||||
}
|
||||
function exit () {
|
||||
cb(null, buf.join('/') || '/');
|
||||
cb(null, path.join(buf.join('/') || '/'));
|
||||
}
|
||||
}
|
||||
|
||||
|
95
lib/path.js
95
lib/path.js
@ -1,44 +1,107 @@
|
||||
|
||||
function validPathPart (p, keepBlanks) {
|
||||
return typeof p === "string" && (p || keepBlanks);
|
||||
}
|
||||
|
||||
exports.join = function () {
|
||||
return exports.normalize(Array.prototype.join.call(arguments, "/"));
|
||||
var args = Array.prototype.slice.call(arguments);
|
||||
// edge case flag to switch into url-resolve-mode
|
||||
var keepBlanks = false;
|
||||
if (args[ args.length - 1 ] === true) {
|
||||
keepBlanks = args.pop();
|
||||
}
|
||||
// return exports.split(args.join("/"), keepBlanks).join("/");
|
||||
var joined = exports.normalizeArray(args, keepBlanks).join("/");
|
||||
return joined;
|
||||
};
|
||||
exports.split = function (path, keepBlanks) {
|
||||
// split based on /, but only if that / is not at the start or end.
|
||||
return exports.normalizeArray(path.split(/^|\/(?!$)/), keepBlanks);
|
||||
};
|
||||
function cleanArray (parts, keepBlanks) {
|
||||
var i = 0;
|
||||
var l = parts.length - 1;
|
||||
var stripped = false;
|
||||
// strip leading empty args
|
||||
while (i < l && !validPathPart(parts[i], keepBlanks)) {
|
||||
stripped = true;
|
||||
i ++;
|
||||
}
|
||||
// strip tailing empty args
|
||||
while (l >= i && !validPathPart(parts[l], keepBlanks)) {
|
||||
stripped = true;
|
||||
l --;
|
||||
}
|
||||
if (stripped) {
|
||||
// if l chopped all the way back to i, then this is empty
|
||||
parts = Array.prototype.slice.call(parts, i, l + 1);
|
||||
}
|
||||
return parts.filter(function (p) { return validPathPart(p, keepBlanks) })
|
||||
.join('/')
|
||||
.split(/^|\/(?!$)/);
|
||||
}
|
||||
exports.normalizeArray = function (original, keepBlanks) {
|
||||
var parts = cleanArray(original, keepBlanks);
|
||||
if (!parts.length || (parts.length === 1 && !parts[0])) return ["."];
|
||||
|
||||
exports.normalizeArray = function (parts, keepBlanks) {
|
||||
var directories = [], prev;
|
||||
for (var i = 0, l = parts.length - 1; i <= l; i++) {
|
||||
// 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, but it's not the first thing, and not the last thing, skip it.
|
||||
if (directory === "" && i !== 0 && i !== l && !keepBlanks) continue;
|
||||
// if it's blank, and we're not keeping blanks, then skip it.
|
||||
if (directory === "" && !keepBlanks) continue;
|
||||
|
||||
// if it's a dot, and there was some previous dir already, then skip it.
|
||||
if (directory === "." && prev !== undefined) continue;
|
||||
|
||||
// if it starts with "", and is a . or .., then skip it.
|
||||
if (directories.length === 1 && directories[0] === "" && (
|
||||
directory === "." || 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 !== "."
|
||||
&& prev !== undefined
|
||||
&& (prev !== "" || keepBlanks)
|
||||
) {
|
||||
directories.pop();
|
||||
prev = directories.slice(-1)[0];
|
||||
} else {
|
||||
if (prev === ".") directories.pop();
|
||||
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, keepBlanks) {
|
||||
return exports.normalizeArray(path.split("/"), keepBlanks).join("/");
|
||||
return exports.join(path, keepBlanks || false);
|
||||
};
|
||||
|
||||
exports.dirname = function (path) {
|
||||
|
17
lib/url.js
17
lib/url.js
@ -272,22 +272,31 @@ function urlResolveObject (source, relative) {
|
||||
else if (dir !== ".") dirs.push(dir);
|
||||
});
|
||||
|
||||
if (mustEndAbs && dirs[0] !== "") {
|
||||
if (mustEndAbs && dirs[0] !== "" && (!dirs[0] || dirs[0].charAt(0) !== "/")) {
|
||||
dirs.unshift("");
|
||||
}
|
||||
srcPath = dirs;
|
||||
}
|
||||
if (hasTrailingSlash && (srcPath.length < 2 || srcPath.slice(-1)[0] !== "")) srcPath.push("");
|
||||
if (hasTrailingSlash && (srcPath.join("/").substr(-1) !== "/")) {
|
||||
srcPath.push("");
|
||||
}
|
||||
|
||||
var isAbsolute = srcPath[0] === "" || (srcPath[0] && srcPath[0].charAt(0) === "/");
|
||||
|
||||
// put the host back
|
||||
if ( psychotic ) source.host = srcPath[0] === "" ? "" : srcPath.shift();
|
||||
if ( psychotic ) {
|
||||
source.host = isAbsolute ? "" : srcPath.shift();
|
||||
}
|
||||
|
||||
mustEndAbs = mustEndAbs || (source.host && srcPath.length);
|
||||
|
||||
if (mustEndAbs && srcPath[0] !== "") srcPath.unshift("");
|
||||
if (mustEndAbs && !isAbsolute) {
|
||||
srcPath.unshift("");
|
||||
}
|
||||
|
||||
source.pathname = srcPath.join("/");
|
||||
|
||||
|
||||
return source;
|
||||
};
|
||||
|
||||
|
@ -37,8 +37,71 @@ assert.equal(path.extname(".path/file.ext"), ".ext");
|
||||
assert.equal(path.extname("file.ext.ext"), ".ext");
|
||||
assert.equal(path.extname("file."), ".");
|
||||
|
||||
assert.equal(path.join(".", "fixtures/b", "..", "/b/c.js"), "fixtures/b/c.js");
|
||||
assert.equal(path.join("/foo", "../../../bar"), "/bar");
|
||||
// path.join tests
|
||||
var failures = [];
|
||||
var joinTests =
|
||||
// arguments result
|
||||
[[['.', 'x/b', '..', '/b/c.js' ], 'x/b/c.js' ]
|
||||
,[['/.', 'x/b', '..', '/b/c.js' ], '/x/b/c.js' ]
|
||||
,[['/foo', '../../../bar' ], '/bar' ]
|
||||
,[['foo', '../../../bar' ], '../../bar' ]
|
||||
,[['foo/', '../../../bar' ], '../../bar' ]
|
||||
,[['foo/x', '../../../bar' ], '../bar' ]
|
||||
,[['foo/x', './bar' ], 'foo/x/bar' ]
|
||||
,[['foo/x/', './bar' ], 'foo/x/bar' ]
|
||||
,[['foo/x/', '.', 'bar' ], 'foo/x/bar' ]
|
||||
,[['./' ], './' ]
|
||||
,[['.', './' ], './' ]
|
||||
,[['.', '.', '.' ], '.' ]
|
||||
,[['.', './', '.' ], '.' ]
|
||||
,[['.', '/./', '.' ], '.' ]
|
||||
,[['.', '/////./', '.' ], '.' ]
|
||||
,[['.' ], '.' ]
|
||||
,[['','.' ], '.' ]
|
||||
,[['', 'foo' ], 'foo' ]
|
||||
,[['foo', '/bar' ], 'foo/bar' ]
|
||||
,[['', '/foo' ], '/foo' ]
|
||||
,[['', '', '/foo' ], '/foo' ]
|
||||
,[['', '', 'foo' ], 'foo' ]
|
||||
,[['foo', '' ], 'foo' ]
|
||||
,[['foo/', '' ], 'foo/' ]
|
||||
,[['foo', '', '/bar' ], 'foo/bar' ]
|
||||
,[['./', '..', '/foo' ], '../foo' ]
|
||||
,[['./', '..', '..', '/foo' ], '../../foo' ]
|
||||
,[['.', '..', '..', '/foo' ], '../../foo' ]
|
||||
,[['', '..', '..', '/foo' ], '../../foo' ]
|
||||
,[['/' ], '/' ]
|
||||
,[['/', '.' ], '/' ]
|
||||
,[['/', '..' ], '/' ]
|
||||
,[['/', '..', '..' ], '/' ]
|
||||
,[['' ], '.' ]
|
||||
,[['', '' ], '.' ]
|
||||
,[[' /foo' ], ' /foo' ]
|
||||
,[[' ', 'foo' ], ' /foo' ]
|
||||
,[[' ', '.' ], ' ' ]
|
||||
,[[' ', '/' ], ' /' ]
|
||||
,[[' ', '' ], ' ' ]
|
||||
// preserving empty path parts, for url resolution case
|
||||
// pass boolean true as LAST argument.
|
||||
,[['', '', true ], '/' ]
|
||||
,[['foo', '', true ], 'foo/' ]
|
||||
,[['foo', '', 'bar', true ], 'foo//bar' ]
|
||||
,[['foo/', '', 'bar', true ], 'foo///bar' ]
|
||||
,[['', true ], '.' ]
|
||||
// filtration of non-strings.
|
||||
,[['x', true, 7, 'y', null, {} ], 'x/y' ]
|
||||
];
|
||||
joinTests.forEach(function (test) {
|
||||
var actual = path.join.apply(path, test[0]);
|
||||
var expected = test[1];
|
||||
var message = "path.join("+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(""))
|
||||
|
||||
|
||||
assert.equal(path.normalize("./fixtures///b/../b/c.js"), "fixtures/b/c.js");
|
||||
assert.equal(path.normalize("./fixtures///b/../b/c.js",true), "fixtures///b/c.js");
|
||||
|
Loading…
x
Reference in New Issue
Block a user