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.
  • Loading branch information
roryrjb authored and chrisdickinson committed Nov 20, 2014
1 parent e41c071 commit 8468714
Show file tree
Hide file tree
Showing 3 changed files with 164 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

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

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
69 changes: 58 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,31 @@ win32.basename = function(path, ext) {


win32.extname = function(path) {
return win32.splitPath(path)[3];
return win32SplitPath(path)[3];
};


win32.format = function(object) {
var root = object.root || '';

This comment has been minimized.

Copy link
@misterdjules

misterdjules Nov 20, 2014

Same comments as for posix.format below.

var dir = object.dir;
var base = object.base || '';
if (dir.slice(dir.length - 1, dir.length) === win32.sep) {
return dir + base;
} else {
return dir + this.sep + base;
}
};


win32.parse = function(string) {
var allParts = win32SplitPath(string);

This comment has been minimized.

Copy link
@misterdjules

misterdjules Nov 20, 2014

Same comments as for posix.parse below.

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 +398,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 +535,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 +554,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 +564,31 @@ posix.basename = function(path, ext) {


posix.extname = function(path) {
return posix.splitPath(path)[3];
return posixSplitPath(path)[3];
};


posix.format = function(object) {
var root = object.root || '';

This comment has been minimized.

Copy link
@misterdjules

misterdjules Nov 20, 2014

Should we assert if typeof object !== 'object'?

var sep = posix.sep;
if (root.indexOf(':') > -1) {

This comment has been minimized.

Copy link
@misterdjules

misterdjules Nov 20, 2014

if object was truthy, but if object.root doesn't have an indexOf method, this code will throw. Should we check if typeof root === 'string'?

sep = '\\';
}
var dir = object.dir + sep;
var base = object.base || '';
return dir + base;
};


posix.parse = function(string) {
var allParts = posixSplitPath(string);

This comment has been minimized.

Copy link
@misterdjules

misterdjules Nov 20, 2014

Should we check if typeof string === 'string'?

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)

This comment has been minimized.

Copy link
@misterdjules

misterdjules Nov 20, 2014

Using any item in allParts without checking if they exist and of the right type looks a bit dangerous to me. Could we add some asserts/throws/checks to check the input?

};
};


Expand Down
64 changes: 64 additions & 0 deletions test/simple/test-path-parse-format.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// 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',

// unc
'\\\\server\\share\\file_path',
'\\\\server two\\shared folder\\file path.zip',
'\\\\teela\\admin$\\system32'

This comment has been minimized.

Copy link
@misterdjules

misterdjules Nov 20, 2014

On Windows, it is also possible to use paths with the \\?\ prefix. I think we should add tests that cover them too. See http://msdn.microsoft.com/en-ca/library/windows/desktop/aa365247(v=vs.85).aspx for more information.


];

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'
];

This comment has been minimized.

Copy link
@misterdjules

misterdjules Nov 20, 2014

I would add more tests with edge cases, like null, non-string values (objects, numbers, undefined, etc.) and check that path.format and path.parse have the correct behavior (they should probably throw on bad input).


check(path.win32, winPaths);
check(path.posix, unixPaths);

function check(path, paths) {
paths.forEach(function(element, index, array) {
var count = index + 1;
console.log(count + ': `' + element + '`');
var output = path.parse(element);
var keys = Object.keys(output);
var values = [];
for (var i = 0; i < Object.keys(output).length; i++) {
values.push(output[keys[i]]);

This comment has been minimized.

Copy link
@misterdjules

misterdjules Nov 20, 2014

What's the use of values in this test? It seems useless to me.

}

assert.strictEqual(path.format(path.parse(element)), element);
assert.strictEqual(path.parse(element).dir, path.dirname(element));
assert.strictEqual(path.parse(element).base, path.basename(element));
assert.strictEqual(path.parse(element).ext, path.extname(element));
})
}

0 comments on commit 8468714

Please sign in to comment.