Skip to content

Commit

Permalink
path: added parse() and format() functions
Browse files Browse the repository at this point in the history
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 nodejs#6976.

Fixes: nodejs/node-v0.x-archive#6976
PR-URL: nodejs/node-v0.x-archive#8750
Reviewed-by: Julien Gilli <[email protected]>
  • Loading branch information
roryrjb authored and chrisdickinson committed Nov 21, 2014
1 parent 6a90a06 commit 2d17193
Show file tree
Hide file tree
Showing 3 changed files with 247 additions and 11 deletions.
42 changes: 42 additions & 0 deletions doc/api/path.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
117 changes: 106 additions & 11 deletions lib/path.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand All @@ -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] || ''),
Expand All @@ -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, '\\');
Expand Down Expand Up @@ -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];

Expand All @@ -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);
Expand All @@ -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)
};
};


Expand All @@ -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)
Expand Down Expand Up @@ -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];

Expand All @@ -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);
Expand All @@ -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)
};
};


Expand Down
99 changes: 99 additions & 0 deletions test/simple/test-path-parse-format.js
Original file line number Diff line number Diff line change
@@ -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));
});
}

0 comments on commit 2d17193

Please sign in to comment.