Improve path parsing on windows
authorBert Belder <bertbelder@gmail.com>
Tue, 6 Sep 2011 02:46:44 +0000 (04:46 +0200)
committerBert Belder <bertbelder@gmail.com>
Tue, 6 Sep 2011 02:47:36 +0000 (04:47 +0200)
Closes #650

lib/path.js
test/simple/test-path.js

index 1c60180..18f7bb7 100644 (file)
@@ -55,15 +55,28 @@ function normalizeArray(parts, allowAboveRoot) {
 
 
 if (isWindows) {
-
-  // Regex to split a filename into [*, dir, basename, ext]
-  // windows version
-  var splitPathRe = /^(.+(?:[\\\/](?!$)|:)|[\\\/])?((?:.+?)?(\.[^.]*)?)$/;
-
   // Regex to split a windows path into three parts: [*, device, slash,
   // tail] windows-only
   var splitDeviceRe =
-      /^([a-zA-Z]:|[\\\/]{2}[^\\\/]+[\\\/][^\\\/]+)?([\\\/])?(.*?)$/;
+      /^([a-zA-Z]:|[\\\/]{2}[^\\\/]+[\\\/][^\\\/]+)?([\\\/])?([\s\S]*?)$/;
+
+  // Regex to split the tail part of the above into [*, dir, basename, ext]
+  var splitTailRe = /^([\s\S]+[\\\/](?!$)|[\\\/])?((?:[\s\S]+?)?(\.[^.]*)?)$/;
+
+   // Function to split a filename into [root, dir, basename, ext]
+   // windows version
+  var splitPath = function(filename) {
+    // Separate device+slash from tail
+    var result = splitDeviceRe.exec(filename),
+        device = (result[1] || '') + (result[2] || ''),
+        tail = result[3] || '';
+    // Split the tail into dir, basename and extension
+    var result2 = splitTailRe.exec(tail),
+        dir = result2[1] || '',
+        basename = result2[2] || '',
+        ext = result2[3] || '';
+    return [device, dir, basename, ext];
+  }
 
   // path.resolve([from ...], to)
   // windows version
@@ -245,9 +258,13 @@ if (isWindows) {
 
 } else /* posix */ {
 
-  // Regex to split a filename into [*, dir, basename, ext]
-  // posix version
-  var splitPathRe = /^([\s\S]+\/(?!$)|\/)?((?:[\s\S]+?)?(\.[^.]*)?)$/;
+  // Split a filename into [root, dir, basename, ext], unix version
+  // 'root' is just a slash, or nothing.
+  var splitPathRe = /^(\/?)([\s\S]+\/(?!$)|\/)?((?:[\s\S]+?)?(\.[^.]*)?)$/;
+  var splitPath = function(filename) {
+    var result = splitPathRe.exec(filename);
+    return [result[1] || '', result[2] || '', result[3] || '', result[4] || ''];
+  };
 
   // path.resolve([from ...], to)
   // posix version
@@ -356,23 +373,26 @@ if (isWindows) {
 
 
 exports.dirname = function(path) {
-  var dir = splitPathRe.exec(path)[1] || '';
-  if (!dir) {
-    // No dirname
+  var result = splitPath(path),
+      root = result[0],
+      dir = result[1];
+
+  if (!root && !dir) {
+    // No dirname whatsoever
     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);
   }
+
+  if (dir) {
+    // It has a dirname, strip trailing slash
+    dir = dir.substring(0, dir.length - 1);
+  }
+
+  return root + dir;
 };
 
 
 exports.basename = function(path, ext) {
-  var f = splitPathRe.exec(path)[2] || '';
+  var f = splitPath(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);
@@ -382,7 +402,7 @@ exports.basename = function(path, ext) {
 
 
 exports.extname = function(path) {
-  return splitPathRe.exec(path)[3] || '';
+  return splitPath(path)[3];
 };
 
 
index 3d72ad7..7fe80ec 100644 (file)
@@ -39,11 +39,43 @@ if (!isWindows) {
 }
 
 assert.equal(path.extname(f), '.js');
+
 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'), '/');
 assert.equal(path.dirname('/'), '/');
+
+if (isWindows) {
+  assert.equal(path.dirname('c:\\'), 'c:\\');
+  assert.equal(path.dirname('c:\\foo'), 'c:\\');
+  assert.equal(path.dirname('c:\\foo\\'), 'c:\\');
+  assert.equal(path.dirname('c:\\foo\\bar'), 'c:\\foo');
+  assert.equal(path.dirname('c:\\foo\\bar\\'), 'c:\\foo');
+  assert.equal(path.dirname('c:\\foo\\bar\\baz'), 'c:\\foo\\bar');
+  assert.equal(path.dirname('\\'), '\\');
+  assert.equal(path.dirname('\\foo'), '\\');
+  assert.equal(path.dirname('\\foo\\'), '\\');
+  assert.equal(path.dirname('\\foo\\bar'), '\\foo');
+  assert.equal(path.dirname('\\foo\\bar\\'), '\\foo');
+  assert.equal(path.dirname('\\foo\\bar\\baz'), '\\foo\\bar');
+  assert.equal(path.dirname('c:'), 'c:');
+  assert.equal(path.dirname('c:foo'), 'c:');
+  assert.equal(path.dirname('c:foo\\'), 'c:');
+  assert.equal(path.dirname('c:foo\\bar'), 'c:foo');
+  assert.equal(path.dirname('c:foo\\bar\\'), 'c:foo');
+  assert.equal(path.dirname('c:foo\\bar\\baz'), 'c:foo\\bar');
+  assert.equal(path.dirname('\\\\unc\\share'), '\\\\unc\\share');
+  assert.equal(path.dirname('\\\\unc\\share\\foo'), '\\\\unc\\share\\');
+  assert.equal(path.dirname('\\\\unc\\share\\foo\\'), '\\\\unc\\share\\');
+  assert.equal(path.dirname('\\\\unc\\share\\foo\\bar'),
+               '\\\\unc\\share\\foo');
+  assert.equal(path.dirname('\\\\unc\\share\\foo\\bar\\'),
+               '\\\\unc\\share\\foo');
+  assert.equal(path.dirname('\\\\unc\\share\\foo\\bar\\baz'),
+               '\\\\unc\\share\\foo\\bar');
+}
+
 path.exists(f, function(y) { assert.equal(y, true) });
 
 assert.equal(path.existsSync(f), true);