path: added parse() and format() functions
The parse() function splits a path and returns an object with the different elements. The format() function is the reverse of this and adds an objects corresponding path elements to make up a string. Fixes #6976. Fixes: https://github.com/joyent/node/issues/6976 PR-URL: https://github.com/joyent/node/pull/8750 Reviewed-by: Julien Gilli <julien.gilli@joyent.com>
This commit is contained in:
parent
6a90a06002
commit
2d17193f20
@ -202,6 +202,48 @@ An example on Windows:
|
||||
// returns
|
||||
['C:\Windows\system32', 'C:\Windows', 'C:\Program Files\nodejs\']
|
||||
|
||||
## path.parse(pathString)
|
||||
|
||||
Returns an object from a path string.
|
||||
|
||||
An example on *nix:
|
||||
|
||||
path.parse('/home/user/dir/file.txt')
|
||||
// returns
|
||||
{
|
||||
root : "/",
|
||||
dir : "/home/user/dir",
|
||||
base : "file.txt",
|
||||
ext : ".txt",
|
||||
name : "file"
|
||||
}
|
||||
|
||||
An example on Windows:
|
||||
|
||||
path.parse('C:\\path\\dir\\index.html')
|
||||
// returns
|
||||
{
|
||||
root : "C:\",
|
||||
dir : "C:\path\dir",
|
||||
base : "index.html",
|
||||
ext : ".html",
|
||||
name : "index"
|
||||
}
|
||||
|
||||
## path.format(pathObject)
|
||||
|
||||
Returns a path string from an object, the opposite of `path.parse` above.
|
||||
|
||||
path.format({
|
||||
root : "/",
|
||||
dir : "/home/user/dir",
|
||||
base : "file.txt",
|
||||
ext : ".txt",
|
||||
name : "file"
|
||||
})
|
||||
// returns
|
||||
'/home/user/dir/file.txt'
|
||||
|
||||
## path.posix
|
||||
|
||||
Provide access to aforementioned `path` methods but always interact in a posix
|
||||
|
117
lib/path.js
117
lib/path.js
@ -54,7 +54,6 @@ function normalizeArray(parts, allowAboveRoot) {
|
||||
return parts;
|
||||
}
|
||||
|
||||
|
||||
// Regex to split a windows path into three parts: [*, device, slash,
|
||||
// tail] windows-only
|
||||
var splitDeviceRe =
|
||||
@ -67,7 +66,7 @@ var splitTailRe =
|
||||
var win32 = {};
|
||||
|
||||
// Function to split a filename into [root, dir, basename, ext]
|
||||
win32.splitPath = function(filename) {
|
||||
function win32SplitPath(filename) {
|
||||
// Separate device+slash from tail
|
||||
var result = splitDeviceRe.exec(filename),
|
||||
device = (result[1] || '') + (result[2] || ''),
|
||||
@ -78,7 +77,7 @@ win32.splitPath = function(filename) {
|
||||
basename = result2[2],
|
||||
ext = result2[3];
|
||||
return [device, dir, basename, ext];
|
||||
};
|
||||
}
|
||||
|
||||
var normalizeUNCRoot = function(device) {
|
||||
return '\\\\' + device.replace(/^[\\\/]+/, '').replace(/[\\\/]+/g, '\\');
|
||||
@ -331,7 +330,7 @@ win32._makeLong = function(path) {
|
||||
|
||||
|
||||
win32.dirname = function(path) {
|
||||
var result = win32.splitPath(path),
|
||||
var result = win32SplitPath(path),
|
||||
root = result[0],
|
||||
dir = result[1];
|
||||
|
||||
@ -350,7 +349,7 @@ win32.dirname = function(path) {
|
||||
|
||||
|
||||
win32.basename = function(path, ext) {
|
||||
var f = win32.splitPath(path)[2];
|
||||
var f = win32SplitPath(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);
|
||||
@ -360,7 +359,57 @@ win32.basename = function(path, ext) {
|
||||
|
||||
|
||||
win32.extname = function(path) {
|
||||
return win32.splitPath(path)[3];
|
||||
return win32SplitPath(path)[3];
|
||||
};
|
||||
|
||||
|
||||
win32.format = function(pathObject) {
|
||||
if (!util.isObject(pathObject)) {
|
||||
throw new TypeError(
|
||||
"Parameter 'pathObject' must be an object, not " + typeof pathObject
|
||||
);
|
||||
}
|
||||
|
||||
var root = pathObject.root || '';
|
||||
|
||||
if (!util.isString(root)) {
|
||||
throw new TypeError(
|
||||
"'pathObject.root' must be a string or undefined, not " +
|
||||
typeof pathObject.root
|
||||
);
|
||||
}
|
||||
|
||||
var dir = pathObject.dir;
|
||||
var base = pathObject.base || '';
|
||||
if (dir.slice(dir.length - 1, dir.length) === win32.sep) {
|
||||
return dir + base;
|
||||
}
|
||||
|
||||
if (dir) {
|
||||
return dir + win32.sep + base;
|
||||
}
|
||||
|
||||
return base;
|
||||
};
|
||||
|
||||
|
||||
win32.parse = function(pathString) {
|
||||
if (!util.isString(pathString)) {
|
||||
throw new TypeError(
|
||||
"Parameter 'pathString' must be a string, not " + typeof pathString
|
||||
);
|
||||
}
|
||||
var allParts = win32SplitPath(pathString);
|
||||
if (!allParts || allParts.length !== 4) {
|
||||
throw new TypeError("Invalid path '" + pathString + "'");
|
||||
}
|
||||
return {
|
||||
root: allParts[0],
|
||||
dir: allParts[0] + allParts[1].slice(0, allParts[1].length - 1),
|
||||
base: allParts[2],
|
||||
ext: allParts[3],
|
||||
name: allParts[2].slice(0, allParts[2].length - allParts[3].length)
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@ -375,9 +424,9 @@ var splitPathRe =
|
||||
var posix = {};
|
||||
|
||||
|
||||
posix.splitPath = function(filename) {
|
||||
function posixSplitPath(filename) {
|
||||
return splitPathRe.exec(filename).slice(1);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// path.resolve([from ...], to)
|
||||
@ -512,7 +561,7 @@ posix._makeLong = function(path) {
|
||||
|
||||
|
||||
posix.dirname = function(path) {
|
||||
var result = posix.splitPath(path),
|
||||
var result = posixSplitPath(path),
|
||||
root = result[0],
|
||||
dir = result[1];
|
||||
|
||||
@ -531,7 +580,7 @@ posix.dirname = function(path) {
|
||||
|
||||
|
||||
posix.basename = function(path, ext) {
|
||||
var f = posix.splitPath(path)[2];
|
||||
var f = posixSplitPath(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);
|
||||
@ -541,7 +590,53 @@ posix.basename = function(path, ext) {
|
||||
|
||||
|
||||
posix.extname = function(path) {
|
||||
return posix.splitPath(path)[3];
|
||||
return posixSplitPath(path)[3];
|
||||
};
|
||||
|
||||
|
||||
posix.format = function(pathObject) {
|
||||
if (!util.isObject(pathObject)) {
|
||||
throw new TypeError(
|
||||
"Parameter 'pathObject' must be an object, not " + typeof pathObject
|
||||
);
|
||||
}
|
||||
|
||||
var root = pathObject.root || '';
|
||||
|
||||
if (!util.isString(root)) {
|
||||
throw new TypeError(
|
||||
"'pathObject.root' must be a string or undefined, not " +
|
||||
typeof pathObject.root
|
||||
);
|
||||
}
|
||||
|
||||
var dir = pathObject.dir ? pathObject.dir + posix.sep : '';
|
||||
var base = pathObject.base || '';
|
||||
return dir + base;
|
||||
};
|
||||
|
||||
|
||||
posix.parse = function(pathString) {
|
||||
if (!util.isString(pathString)) {
|
||||
throw new TypeError(
|
||||
"Parameter 'pathString' must be a string, not " + typeof pathString
|
||||
);
|
||||
}
|
||||
var allParts = posixSplitPath(pathString);
|
||||
if (!allParts || allParts.length !== 4) {
|
||||
throw new TypeError("Invalid path '" + pathString + "'");
|
||||
}
|
||||
allParts[1] = allParts[1] || '';
|
||||
allParts[2] = allParts[2] || '';
|
||||
allParts[3] = allParts[3] || '';
|
||||
|
||||
return {
|
||||
root: allParts[0],
|
||||
dir: allParts[0] + allParts[1].slice(0, allParts[1].length - 1),
|
||||
base: allParts[2],
|
||||
ext: allParts[3],
|
||||
name: allParts[2].slice(0, allParts[2].length - allParts[3].length)
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
|
99
test/simple/test-path-parse-format.js
Normal file
99
test/simple/test-path-parse-format.js
Normal file
@ -0,0 +1,99 @@
|
||||
// Copyright Joyent, Inc. and other Node contributors.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a
|
||||
// copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
// persons to whom the Software is furnished to do so, subject to the
|
||||
// following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included
|
||||
// in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
||||
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
// USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
var assert = require('assert');
|
||||
var path = require('path');
|
||||
|
||||
var winPaths = [
|
||||
'C:\\path\\dir\\index.html',
|
||||
'C:\\another_path\\DIR\\1\\2\\33\\index',
|
||||
'another_path\\DIR with spaces\\1\\2\\33\\index',
|
||||
'\\foo\\C:',
|
||||
'file',
|
||||
'.\\file',
|
||||
|
||||
// unc
|
||||
'\\\\server\\share\\file_path',
|
||||
'\\\\server two\\shared folder\\file path.zip',
|
||||
'\\\\teela\\admin$\\system32',
|
||||
'\\\\?\\UNC\\server\\share'
|
||||
|
||||
];
|
||||
|
||||
var unixPaths = [
|
||||
'/home/user/dir/file.txt',
|
||||
'/home/user/a dir/another File.zip',
|
||||
'/home/user/a dir//another&File.',
|
||||
'/home/user/a$$$dir//another File.zip',
|
||||
'user/dir/another File.zip',
|
||||
'file',
|
||||
'.\\file',
|
||||
'./file',
|
||||
'C:\\foo'
|
||||
];
|
||||
|
||||
var errors = [
|
||||
{method: 'parse', input: [null], message: /Parameter 'pathString' must be a string, not/},
|
||||
{method: 'parse', input: [{}], message: /Parameter 'pathString' must be a string, not object/},
|
||||
{method: 'parse', input: [true], message: /Parameter 'pathString' must be a string, not boolean/},
|
||||
{method: 'parse', input: [1], message: /Parameter 'pathString' must be a string, not number/},
|
||||
{method: 'parse', input: [], message: /Parameter 'pathString' must be a string, not undefined/},
|
||||
// {method: 'parse', input: [''], message: /Invalid path/}, // omitted because it's hard to trigger!
|
||||
{method: 'format', input: [null], message: /Parameter 'pathObject' must be an object, not/},
|
||||
{method: 'format', input: [''], message: /Parameter 'pathObject' must be an object, not string/},
|
||||
{method: 'format', input: [true], message: /Parameter 'pathObject' must be an object, not boolean/},
|
||||
{method: 'format', input: [1], message: /Parameter 'pathObject' must be an object, not number/},
|
||||
{method: 'format', input: [{root: true}], message: /'pathObject.root' must be a string or undefined, not boolean/},
|
||||
{method: 'format', input: [{root: 12}], message: /'pathObject.root' must be a string or undefined, not number/},
|
||||
];
|
||||
|
||||
check(path.win32, winPaths);
|
||||
check(path.posix, unixPaths);
|
||||
checkErrors(path.win32);
|
||||
checkErrors(path.posix);
|
||||
|
||||
function checkErrors(path) {
|
||||
errors.forEach(function(errorCase) {
|
||||
try {
|
||||
path[errorCase.method].apply(path, errorCase.input);
|
||||
} catch(err) {
|
||||
assert.ok(err instanceof TypeError);
|
||||
assert.ok(
|
||||
errorCase.message.test(err.message),
|
||||
'expected ' + errorCase.message + ' to match ' + err.message
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
assert.fail('should have thrown');
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function check(path, paths) {
|
||||
paths.forEach(function(element, index, array) {
|
||||
var output = path.parse(element);
|
||||
assert.strictEqual(path.format(output), element);
|
||||
assert.strictEqual(output.dir, output.dir ? path.dirname(element) : '');
|
||||
assert.strictEqual(output.base, path.basename(element));
|
||||
assert.strictEqual(output.ext, path.extname(element));
|
||||
});
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user