diff --git a/doc/api/path.markdown b/doc/api/path.markdown index 0e9a78b15b0..054159533af 100644 --- a/doc/api/path.markdown +++ b/doc/api/path.markdown @@ -201,3 +201,13 @@ An example on Windows: process.env.PATH.split(path.delimiter) // returns ['C:\Windows\system32', 'C:\Windows', 'C:\Program Files\nodejs\'] + +## path.posix + +Provide access to aforementioned `path` methods but always interact in a posix +compatible way. + +## path.win32 + +Provide access to aforementioned `path` methods but always interact in a win32 +compatible way. diff --git a/lib/path.js b/lib/path.js index 6af430ec478..2cab82acd5e 100644 --- a/lib/path.js +++ b/lib/path.js @@ -55,401 +55,283 @@ function normalizeArray(parts, allowAboveRoot) { } -if (isWindows) { - // Regex to split a windows path into three parts: [*, device, slash, - // tail] windows-only - var splitDeviceRe = - /^([a-zA-Z]:|[\\\/]{2}[^\\\/]+[\\\/]+[^\\\/]+)?([\\\/])?([\s\S]*?)$/; +// Regex to split a windows path into three parts: [*, device, slash, +// tail] windows-only +var splitDeviceRe = + /^([a-zA-Z]:|[\\\/]{2}[^\\\/]+[\\\/]+[^\\\/]+)?([\\\/])?([\s\S]*?)$/; - // Regex to split the tail part of the above into [*, dir, basename, ext] - var splitTailRe = - /^([\s\S]*?)((?:\.{1,2}|[^\\\/]+?|)(\.[^.\/\\]*|))(?:[\\\/]*)$/; +// Regex to split the tail part of the above into [*, dir, basename, ext] +var splitTailRe = + /^([\s\S]*?)((?:\.{1,2}|[^\\\/]+?|)(\.[^.\/\\]*|))(?:[\\\/]*)$/; - // 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]; - }; +var win32 = {}; - var normalizeUNCRoot = function(device) { - return '\\\\' + device.replace(/^[\\\/]+/, '').replace(/[\\\/]+/g, '\\'); - }; +// Function to split a filename into [root, dir, basename, ext] +win32.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 - exports.resolve = function() { - var resolvedDevice = '', - resolvedTail = '', - resolvedAbsolute = false; +var normalizeUNCRoot = function(device) { + return '\\\\' + device.replace(/^[\\\/]+/, '').replace(/[\\\/]+/g, '\\'); +}; - 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 + '\\'; - } - } +// path.resolve([from ...], to) +win32.resolve = function() { + var resolvedDevice = '', + resolvedTail = '', + resolvedAbsolute = false; - // Skip empty and invalid entries - if (!util.isString(path)) { - throw new TypeError('Arguments to path.resolve must be strings'); - } else if (!path) { - continue; - } - - var result = splitDeviceRe.exec(path), - device = result[1] || '', - isUnc = device && device.charAt(1) !== ':', - isAbsolute = exports.isAbsolute(path), - 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; + 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 + '\\'; } } - // 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); + // Skip empty and invalid entries + if (!util.isString(path)) { + throw new TypeError('Arguments to path.resolve must be strings'); + } else if (!path) { + continue; } - // 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 - - function f(p) { - return !!p; - } - - resolvedTail = normalizeArray(resolvedTail.split(/[\\\/]+/).filter(f), - !resolvedAbsolute).join('\\'); - - // If device is a drive letter, we'll normalize to lower case. - if (resolvedDevice && resolvedDevice.charAt(1) === ':') { - resolvedDevice = resolvedDevice[0].toLowerCase() + - resolvedDevice.substr(1); - } - - return (resolvedDevice + (resolvedAbsolute ? '\\' : '') + resolvedTail) || - '.'; - }; - - // windows version - exports.normalize = function(path) { var result = splitDeviceRe.exec(path), device = result[1] || '', isUnc = device && device.charAt(1) !== ':', - isAbsolute = exports.isAbsolute(path), - tail = result[3], - trailingSlash = /[\\\/]$/.test(tail); + isAbsolute = win32.isAbsolute(path), + tail = result[3]; - // If device is a drive letter, we'll normalize to lower case. - if (device && device.charAt(1) === ':') { - device = device[0].toLowerCase() + device.substr(1); + if (device && + resolvedDevice && + device.toLowerCase() !== resolvedDevice.toLowerCase()) { + // This path points to another device so it is not applicable + continue; } - // Normalize the tail path - tail = normalizeArray(tail.split(/[\\\/]+/).filter(function(p) { - return !!p; - }), !isAbsolute).join('\\'); - - if (!tail && !isAbsolute) { - tail = '.'; + if (!resolvedDevice) { + resolvedDevice = device; } - if (tail && trailingSlash) { - tail += '\\'; + if (!resolvedAbsolute) { + resolvedTail = tail + '\\' + resolvedTail; + resolvedAbsolute = isAbsolute; } - // 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); + 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) + + // Normalize the tail path + + function f(p) { + return !!p; + } + + resolvedTail = normalizeArray(resolvedTail.split(/[\\\/]+/).filter(f), + !resolvedAbsolute).join('\\'); + + // If device is a drive letter, we'll normalize to lower case. + if (resolvedDevice && resolvedDevice.charAt(1) === ':') { + resolvedDevice = resolvedDevice[0].toLowerCase() + + resolvedDevice.substr(1); + } + + return (resolvedDevice + (resolvedAbsolute ? '\\' : '') + resolvedTail) || + '.'; +}; + + +win32.normalize = function(path) { + var result = splitDeviceRe.exec(path), + device = result[1] || '', + isUnc = device && device.charAt(1) !== ':', + isAbsolute = win32.isAbsolute(path), + tail = result[3], + trailingSlash = /[\\\/]$/.test(tail); + + // If device is a drive letter, we'll normalize to lower case. + if (device && device.charAt(1) === ':') { + device = device[0].toLowerCase() + device.substr(1); + } + + // Normalize the tail path + tail = normalizeArray(tail.split(/[\\\/]+/).filter(function(p) { + return !!p; + }), !isAbsolute).join('\\'); + + if (!tail && !isAbsolute) { + tail = '.'; + } + if (tail && trailingSlash) { + tail += '\\'; + } + + // 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; +}; + + +win32.isAbsolute = function(path) { + var result = splitDeviceRe.exec(path), + device = result[1] || '', + isUnc = !!device && device.charAt(1) !== ':'; + // UNC paths are always absolute + return !!result[2] || isUnc; +}; + +win32.join = function() { + function f(p) { + if (!util.isString(p)) { + throw new TypeError('Arguments to path.join must be strings'); + } + return p; + } + + var paths = Array.prototype.filter.call(arguments, f); + 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) { + from = win32.resolve(from); + to = win32.resolve(to); + + // windows is not case sensitive + var lowerFrom = from.toLowerCase(); + var lowerTo = to.toLowerCase(); + + function trim(arr) { + var start = 0; + for (; start < arr.length; start++) { + if (arr[start] !== '') break; } - return device + (isAbsolute ? '\\' : '') + tail; - }; - - // windows version - exports.isAbsolute = function(path) { - var result = splitDeviceRe.exec(path), - device = result[1] || '', - isUnc = !!device && device.charAt(1) !== ':'; - // UNC paths are always absolute - return !!result[2] || isUnc; - }; - - // windows version - exports.join = function() { - function f(p) { - if (!util.isString(p)) { - throw new TypeError('Arguments to path.join must be strings'); - } - return p; + var end = arr.length - 1; + for (; end >= 0; end--) { + if (arr[end] !== '') break; } - var paths = Array.prototype.filter.call(arguments, f); - var joined = paths.join('\\'); + if (start > end) return []; + return arr.slice(start, end + 1); + } - // 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,}/, '\\'); + var toParts = trim(to.split('\\')); + + var lowerFromParts = trim(lowerFrom.split('\\')); + var lowerToParts = trim(lowerTo.split('\\')); + + 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; } + } - return exports.normalize(joined); - }; + if (samePartsLength == 0) { + return to; + } - // 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' - // windows version - exports.relative = function(from, to) { - from = exports.resolve(from); - to = exports.resolve(to); + var outputParts = []; + for (var i = samePartsLength; i < lowerFromParts.length; i++) { + outputParts.push('..'); + } - // windows is not case sensitive - var lowerFrom = from.toLowerCase(); - var lowerTo = to.toLowerCase(); + outputParts = outputParts.concat(toParts.slice(samePartsLength)); - function trim(arr) { - var start = 0; - for (; start < arr.length; start++) { - if (arr[start] !== '') break; - } - - var end = arr.length - 1; - for (; end >= 0; end--) { - if (arr[end] !== '') break; - } - - if (start > end) return []; - return arr.slice(start, end + 1); - } - - var toParts = trim(to.split('\\')); - - var lowerFromParts = trim(lowerFrom.split('\\')); - var lowerToParts = trim(lowerTo.split('\\')); - - 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; - } - } - - if (samePartsLength == 0) { - return to; - } - - var outputParts = []; - for (var i = samePartsLength; i < lowerFromParts.length; i++) { - outputParts.push('..'); - } - - outputParts = outputParts.concat(toParts.slice(samePartsLength)); - - return outputParts.join('\\'); - }; - - exports.sep = '\\'; - exports.delimiter = ';'; - -} else /* posix */ { - - // Split a filename into [root, dir, basename, ext], unix version - // 'root' is just a slash, or nothing. - var splitPathRe = - /^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/; - var splitPath = function(filename) { - return splitPathRe.exec(filename).slice(1); - }; - - // path.resolve([from ...], to) - // posix version - exports.resolve = function() { - var resolvedPath = '', - resolvedAbsolute = false; - - for (var i = arguments.length - 1; i >= -1 && !resolvedAbsolute; i--) { - var path = (i >= 0) ? arguments[i] : process.cwd(); - - // Skip empty and invalid entries - if (!util.isString(path)) { - throw new TypeError('Arguments to path.resolve must be strings'); - } else if (!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 = exports.isAbsolute(path), - trailingSlash = path[path.length - 1] === '/', - segments = path.split('/'), - nonEmptySegments = []; - - // Normalize the path - for (var i = 0; i < segments.length; i++) { - if (segments[i]) { - nonEmptySegments.push(segments[i]); - } - } - path = normalizeArray(nonEmptySegments, !isAbsolute).join('/'); - - if (!path && !isAbsolute) { - path = '.'; - } - if (path && trailingSlash) { - path += '/'; - } - - return (isAbsolute ? '/' : '') + path; - }; - - // posix version - exports.isAbsolute = function(path) { - return path.charAt(0) === '/'; - }; - - // posix version - exports.join = function() { - var path = ''; - for (var i = 0; i < arguments.length; i++) { - var segment = arguments[i]; - if (!util.isString(segment)) { - throw new TypeError('Arguments to path.join must be strings'); - } - if (segment) { - if (!path) { - path += segment; - } else { - path += '/' + segment; - } - } - } - return exports.normalize(path); - }; + return outputParts.join('\\'); +}; - // path.relative(from, to) - // posix version - exports.relative = function(from, to) { - from = exports.resolve(from).substr(1); - to = exports.resolve(to).substr(1); +win32._makeLong = function(path) { + // Note: this will *probably* throw somewhere. + if (!util.isString(path)) + return path; - function trim(arr) { - var start = 0; - for (; start < arr.length; start++) { - if (arr[start] !== '') break; - } + if (!path) { + return ''; + } - var end = arr.length - 1; - for (; end >= 0; end--) { - if (arr[end] !== '') break; - } + var resolvedPath = win32.resolve(path); - if (start > end) return []; - return arr.slice(start, end + 1); - } + 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); + } - var fromParts = trim(from.split('/')); - var toParts = trim(to.split('/')); + return path; +}; - 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 i = samePartsLength; i < fromParts.length; i++) { - outputParts.push('..'); - } - - outputParts = outputParts.concat(toParts.slice(samePartsLength)); - - return outputParts.join('/'); - }; - - exports.sep = '/'; - exports.delimiter = ':'; -} - -exports.dirname = function(path) { - var result = splitPath(path), +win32.dirname = function(path) { + var result = win32.splitPath(path), root = result[0], dir = result[1]; @@ -467,8 +349,8 @@ exports.dirname = function(path) { }; -exports.basename = function(path, ext) { - var f = splitPath(path)[2]; +win32.basename = function(path, ext) { + var f = win32.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); @@ -477,47 +359,200 @@ exports.basename = function(path, ext) { }; -exports.extname = function(path) { - return splitPath(path)[3]; +win32.extname = function(path) { + return win32.splitPath(path)[3]; }; -exports.exists = util.deprecate(function(path, callback) { - require('fs').exists(path, callback); -}, 'path.exists is now called `fs.exists`.'); +win32.sep = '\\'; +win32.delimiter = ';'; -exports.existsSync = util.deprecate(function(path) { - return require('fs').existsSync(path); -}, 'path.existsSync is now called `fs.existsSync`.'); +// Split a filename into [root, dir, basename, ext], unix version +// 'root' is just a slash, or nothing. +var splitPathRe = + /^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/; +var posix = {}; -if (isWindows) { - exports._makeLong = function(path) { - // Note: this will *probably* throw somewhere. - if (!util.isString(path)) - return path; +posix.splitPath = function(filename) { + return splitPathRe.exec(filename).slice(1); +}; - if (!path) { - return ''; + +// path.resolve([from ...], to) +// posix version +posix.resolve = function() { + var resolvedPath = '', + resolvedAbsolute = false; + + for (var i = arguments.length - 1; i >= -1 && !resolvedAbsolute; i--) { + var path = (i >= 0) ? arguments[i] : process.cwd(); + + // Skip empty and invalid entries + if (!util.isString(path)) { + throw new TypeError('Arguments to path.resolve must be strings'); + } else if (!path) { + continue; } - var resolvedPath = exports.resolve(path); + resolvedPath = path + '/' + resolvedPath; + resolvedAbsolute = path.charAt(0) === '/'; + } - 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); + // 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 +posix.normalize = function(path) { + var isAbsolute = posix.isAbsolute(path), + trailingSlash = path.substr(-1) === '/', + segments = path.split('/'), + nonEmptySegments = []; + + // Normalize the path + for (var i = 0; i < segments.length; i++) { + if (segments[i]) { + nonEmptySegments.push(segments[i]); + } + } + path = normalizeArray(nonEmptySegments, !isAbsolute).join('/'); + + if (!path && !isAbsolute) { + path = '.'; + } + if (path && trailingSlash) { + path += '/'; + } + + return (isAbsolute ? '/' : '') + path; +}; + +// posix version +posix.isAbsolute = function(path) { + return path.charAt(0) === '/'; +}; + +// posix version +posix.join = function() { + var path = ''; + for (var i = 0; i < arguments.length; i++) { + var segment = arguments[i]; + if (!util.isString(segment)) { + throw new TypeError('Arguments to path.join must be strings'); + } + if (segment) { + if (!path) { + path += segment; + } else { + path += '/' + segment; + } + } + } + return posix.normalize(path); +}; + + +// path.relative(from, to) +// posix version +posix.relative = function(from, to) { + from = posix.resolve(from).substr(1); + to = posix.resolve(to).substr(1); + + function trim(arr) { + var start = 0; + for (; start < arr.length; start++) { + if (arr[start] !== '') break; } - return path; - }; -} else { - exports._makeLong = function(path) { - return path; - }; -} + var end = arr.length - 1; + for (; end >= 0; end--) { + if (arr[end] !== '') break; + } + + if (start > end) return []; + return arr.slice(start, end + 1); + } + + var fromParts = trim(from.split('/')); + var toParts = trim(to.split('/')); + + 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 i = samePartsLength; i < fromParts.length; i++) { + outputParts.push('..'); + } + + outputParts = outputParts.concat(toParts.slice(samePartsLength)); + + return outputParts.join('/'); +}; + + +posix._makeLong = function(path) { + return path; +}; + + +posix.dirname = function(path) { + var result = posix.splitPath(path), + root = result[0], + 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; +}; + + +posix.basename = function(path, ext) { + var f = posix.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); + } + return f; +}; + + +posix.extname = function(path) { + return posix.splitPath(path)[3]; +}; + + +posix.sep = '/'; +posix.delimiter = ':'; + + +if (isWindows) + module.exports = win32; +else /* posix */ + module.exports = posix; + +module.exports.posix = posix; +module.exports.win32 = win32; diff --git a/test/simple/test-path.js b/test/simple/test-path.js index b4247f8bf89..cdd59bcd5d9 100644 --- a/test/simple/test-path.js +++ b/test/simple/test-path.js @@ -37,22 +37,19 @@ assert.equal(path.basename('basename.ext'), 'basename.ext'); assert.equal(path.basename('basename.ext/'), 'basename.ext'); assert.equal(path.basename('basename.ext//'), 'basename.ext'); -if (isWindows) { - // On Windows a backslash acts as a path separator. - assert.equal(path.basename('\\dir\\basename.ext'), 'basename.ext'); - assert.equal(path.basename('\\basename.ext'), 'basename.ext'); - assert.equal(path.basename('basename.ext'), 'basename.ext'); - assert.equal(path.basename('basename.ext\\'), 'basename.ext'); - assert.equal(path.basename('basename.ext\\\\'), 'basename.ext'); +// On Windows a backslash acts as a path separator. +assert.equal(path.win32.basename('\\dir\\basename.ext'), 'basename.ext'); +assert.equal(path.win32.basename('\\basename.ext'), 'basename.ext'); +assert.equal(path.win32.basename('basename.ext'), 'basename.ext'); +assert.equal(path.win32.basename('basename.ext\\'), 'basename.ext'); +assert.equal(path.win32.basename('basename.ext\\\\'), 'basename.ext'); -} else { - // On unix a backslash is just treated as any other character. - assert.equal(path.basename('\\dir\\basename.ext'), '\\dir\\basename.ext'); - assert.equal(path.basename('\\basename.ext'), '\\basename.ext'); - assert.equal(path.basename('basename.ext'), 'basename.ext'); - assert.equal(path.basename('basename.ext\\'), 'basename.ext\\'); - assert.equal(path.basename('basename.ext\\\\'), 'basename.ext\\\\'); -} +// On unix a backslash is just treated as any other character. +assert.equal(path.posix.basename('\\dir\\basename.ext'), '\\dir\\basename.ext'); +assert.equal(path.posix.basename('\\basename.ext'), '\\basename.ext'); +assert.equal(path.posix.basename('basename.ext'), 'basename.ext'); +assert.equal(path.posix.basename('basename.ext\\'), 'basename.ext\\'); +assert.equal(path.posix.basename('basename.ext\\\\'), 'basename.ext\\\\'); // POSIX filenames may include control characters // c.f. http://www.dwheeler.com/essays/fixing-unix-linux-filenames.html @@ -73,35 +70,33 @@ assert.equal(path.dirname(''), '.'); assert.equal(path.dirname('/'), '/'); 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'); -} +assert.equal(path.win32.dirname('c:\\'), 'c:\\'); +assert.equal(path.win32.dirname('c:\\foo'), 'c:\\'); +assert.equal(path.win32.dirname('c:\\foo\\'), 'c:\\'); +assert.equal(path.win32.dirname('c:\\foo\\bar'), 'c:\\foo'); +assert.equal(path.win32.dirname('c:\\foo\\bar\\'), 'c:\\foo'); +assert.equal(path.win32.dirname('c:\\foo\\bar\\baz'), 'c:\\foo\\bar'); +assert.equal(path.win32.dirname('\\'), '\\'); +assert.equal(path.win32.dirname('\\foo'), '\\'); +assert.equal(path.win32.dirname('\\foo\\'), '\\'); +assert.equal(path.win32.dirname('\\foo\\bar'), '\\foo'); +assert.equal(path.win32.dirname('\\foo\\bar\\'), '\\foo'); +assert.equal(path.win32.dirname('\\foo\\bar\\baz'), '\\foo\\bar'); +assert.equal(path.win32.dirname('c:'), 'c:'); +assert.equal(path.win32.dirname('c:foo'), 'c:'); +assert.equal(path.win32.dirname('c:foo\\'), 'c:'); +assert.equal(path.win32.dirname('c:foo\\bar'), 'c:foo'); +assert.equal(path.win32.dirname('c:foo\\bar\\'), 'c:foo'); +assert.equal(path.win32.dirname('c:foo\\bar\\baz'), 'c:foo\\bar'); +assert.equal(path.win32.dirname('\\\\unc\\share'), '\\\\unc\\share'); +assert.equal(path.win32.dirname('\\\\unc\\share\\foo'), '\\\\unc\\share\\'); +assert.equal(path.win32.dirname('\\\\unc\\share\\foo\\'), '\\\\unc\\share\\'); +assert.equal(path.win32.dirname('\\\\unc\\share\\foo\\bar'), + '\\\\unc\\share\\foo'); +assert.equal(path.win32.dirname('\\\\unc\\share\\foo\\bar\\'), + '\\\\unc\\share\\foo'); +assert.equal(path.win32.dirname('\\\\unc\\share\\foo\\bar\\baz'), + '\\\\unc\\share\\foo\\bar'); assert.equal(path.extname(''), ''); @@ -146,28 +141,25 @@ assert.equal(path.extname('file//'), ''); assert.equal(path.extname('file./'), '.'); assert.equal(path.extname('file.//'), '.'); -if (isWindows) { - // On windows, backspace is a path separator. - assert.equal(path.extname('.\\'), ''); - assert.equal(path.extname('..\\'), ''); - assert.equal(path.extname('file.ext\\'), '.ext'); - assert.equal(path.extname('file.ext\\\\'), '.ext'); - assert.equal(path.extname('file\\'), ''); - assert.equal(path.extname('file\\\\'), ''); - assert.equal(path.extname('file.\\'), '.'); - assert.equal(path.extname('file.\\\\'), '.'); +// On windows, backspace is a path separator. +assert.equal(path.win32.extname('.\\'), ''); +assert.equal(path.win32.extname('..\\'), ''); +assert.equal(path.win32.extname('file.ext\\'), '.ext'); +assert.equal(path.win32.extname('file.ext\\\\'), '.ext'); +assert.equal(path.win32.extname('file\\'), ''); +assert.equal(path.win32.extname('file\\\\'), ''); +assert.equal(path.win32.extname('file.\\'), '.'); +assert.equal(path.win32.extname('file.\\\\'), '.'); -} else { - // On unix, backspace is a valid name component like any other character. - assert.equal(path.extname('.\\'), ''); - assert.equal(path.extname('..\\'), '.\\'); - assert.equal(path.extname('file.ext\\'), '.ext\\'); - assert.equal(path.extname('file.ext\\\\'), '.ext\\\\'); - assert.equal(path.extname('file\\'), ''); - assert.equal(path.extname('file\\\\'), ''); - assert.equal(path.extname('file.\\'), '.\\'); - assert.equal(path.extname('file.\\\\'), '.\\\\'); -} +// On unix, backspace is a valid name component like any other character. +assert.equal(path.posix.extname('.\\'), ''); +assert.equal(path.posix.extname('..\\'), '.\\'); +assert.equal(path.posix.extname('file.ext\\'), '.ext\\'); +assert.equal(path.posix.extname('file.ext\\\\'), '.ext\\\\'); +assert.equal(path.posix.extname('file\\'), ''); +assert.equal(path.posix.extname('file\\\\'), ''); +assert.equal(path.posix.extname('file.\\'), '.\\'); +assert.equal(path.posix.extname('file.\\\\'), '.\\\\'); // path.join tests var failures = []; @@ -294,23 +286,21 @@ joinThrowTests.forEach(function(test) { // 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'); - assert.equal(path.normalize('//server/share/dir/file.ext'), - '\\\\server\\share\\dir\\file.ext'); -} 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.win32.normalize('./fixtures///b/../b/c.js'), + 'fixtures\\b\\c.js'); +assert.equal(path.win32.normalize('/foo/../../../bar'), '\\bar'); +assert.equal(path.win32.normalize('a//b//../b'), 'a\\b'); +assert.equal(path.win32.normalize('a//b//./c'), 'a\\b\\c'); +assert.equal(path.win32.normalize('a//b//.'), 'a\\b'); +assert.equal(path.win32.normalize('//server/share/dir/file.ext'), + '\\\\server\\share\\dir\\file.ext'); + +assert.equal(path.posix.normalize('./fixtures///b/../b/c.js'), + 'fixtures/b/c.js'); +assert.equal(path.posix.normalize('/foo/../../../bar'), '/bar'); +assert.equal(path.posix.normalize('a//b//../b'), 'a/b'); +assert.equal(path.posix.normalize('a//b//./c'), 'a/b/c'); +assert.equal(path.posix.normalize('a//b//.'), 'a/b'); // path.resolve tests if (isWindows) { @@ -352,21 +342,19 @@ resolveTests.forEach(function(test) { assert.equal(failures.length, 0, failures.join('')); // path.isAbsolute tests -if (isWindows) { - assert.equal(path.isAbsolute('//server/file'), true); - assert.equal(path.isAbsolute('\\\\server\\file'), true); - assert.equal(path.isAbsolute('C:/Users/'), true); - assert.equal(path.isAbsolute('C:\\Users\\'), true); - assert.equal(path.isAbsolute('C:cwd/another'), false); - assert.equal(path.isAbsolute('C:cwd\\another'), false); - assert.equal(path.isAbsolute('directory/directory'), false); - assert.equal(path.isAbsolute('directory\\directory'), false); -} else { - assert.equal(path.isAbsolute('/home/foo'), true); - assert.equal(path.isAbsolute('/home/foo/..'), true); - assert.equal(path.isAbsolute('bar/'), false); - assert.equal(path.isAbsolute('./baz'), false); -} +assert.equal(path.win32.isAbsolute('//server/file'), true); +assert.equal(path.win32.isAbsolute('\\\\server\\file'), true); +assert.equal(path.win32.isAbsolute('C:/Users/'), true); +assert.equal(path.win32.isAbsolute('C:\\Users\\'), true); +assert.equal(path.win32.isAbsolute('C:cwd/another'), false); +assert.equal(path.win32.isAbsolute('C:cwd\\another'), false); +assert.equal(path.win32.isAbsolute('directory/directory'), false); +assert.equal(path.win32.isAbsolute('directory\\directory'), false); + +assert.equal(path.posix.isAbsolute('/home/foo'), true); +assert.equal(path.posix.isAbsolute('/home/foo/..'), true); +assert.equal(path.posix.isAbsolute('bar/'), false); +assert.equal(path.posix.isAbsolute('./baz'), false); // path.relative tests if (isWindows) { @@ -405,20 +393,20 @@ relativeTests.forEach(function(test) { }); assert.equal(failures.length, 0, failures.join('')); -// path.sep tests -if (isWindows) { - // windows - assert.equal(path.sep, '\\'); -} else { - // posix - assert.equal(path.sep, '/'); -} +// windows +assert.equal(path.win32.sep, '\\'); +// posix +assert.equal(path.posix.sep, '/'); // path.delimiter tests -if (isWindows) { - // windows - assert.equal(path.delimiter, ';'); -} else { - // posix - assert.equal(path.delimiter, ':'); -} +// windows +assert.equal(path.win32.delimiter, ';'); + +// posix +assert.equal(path.posix.delimiter, ':'); + + +if (isWindows) + assert.deepEqual(path, path.win32, 'should be win32 path module'); +else + assert.deepEqual(path, path.posix, 'should be posix path module');