Skip to content

Commit 2d17193

Browse files
roryrjbchrisdickinson
authored andcommitted
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 nodejs#6976. Fixes: nodejs/node-v0.x-archive#6976 PR-URL: nodejs/node-v0.x-archive#8750 Reviewed-by: Julien Gilli <julien.gilli@joyent.com>
1 parent 6a90a06 commit 2d17193

3 files changed

Lines changed: 247 additions & 11 deletions

File tree

doc/api/path.markdown

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,48 @@ An example on Windows:
202202
// returns
203203
['C:\Windows\system32', 'C:\Windows', 'C:\Program Files\nodejs\']
204204

205+
## path.parse(pathString)
206+
207+
Returns an object from a path string.
208+
209+
An example on *nix:
210+
211+
path.parse('/home/user/dir/file.txt')
212+
// returns
213+
{
214+
root : "/",
215+
dir : "/home/user/dir",
216+
base : "file.txt",
217+
ext : ".txt",
218+
name : "file"
219+
}
220+
221+
An example on Windows:
222+
223+
path.parse('C:\\path\\dir\\index.html')
224+
// returns
225+
{
226+
root : "C:\",
227+
dir : "C:\path\dir",
228+
base : "index.html",
229+
ext : ".html",
230+
name : "index"
231+
}
232+
233+
## path.format(pathObject)
234+
235+
Returns a path string from an object, the opposite of `path.parse` above.
236+
237+
path.format({
238+
root : "/",
239+
dir : "/home/user/dir",
240+
base : "file.txt",
241+
ext : ".txt",
242+
name : "file"
243+
})
244+
// returns
245+
'/home/user/dir/file.txt'
246+
205247
## path.posix
206248

207249
Provide access to aforementioned `path` methods but always interact in a posix

lib/path.js

Lines changed: 106 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,6 @@ function normalizeArray(parts, allowAboveRoot) {
5454
return parts;
5555
}
5656

57-
5857
// Regex to split a windows path into three parts: [*, device, slash,
5958
// tail] windows-only
6059
var splitDeviceRe =
@@ -67,7 +66,7 @@ var splitTailRe =
6766
var win32 = {};
6867

6968
// Function to split a filename into [root, dir, basename, ext]
70-
win32.splitPath = function(filename) {
69+
function win32SplitPath(filename) {
7170
// Separate device+slash from tail
7271
var result = splitDeviceRe.exec(filename),
7372
device = (result[1] || '') + (result[2] || ''),
@@ -78,7 +77,7 @@ win32.splitPath = function(filename) {
7877
basename = result2[2],
7978
ext = result2[3];
8079
return [device, dir, basename, ext];
81-
};
80+
}
8281

8382
var normalizeUNCRoot = function(device) {
8483
return '\\\\' + device.replace(/^[\\\/]+/, '').replace(/[\\\/]+/g, '\\');
@@ -331,7 +330,7 @@ win32._makeLong = function(path) {
331330

332331

333332
win32.dirname = function(path) {
334-
var result = win32.splitPath(path),
333+
var result = win32SplitPath(path),
335334
root = result[0],
336335
dir = result[1];
337336

@@ -350,7 +349,7 @@ win32.dirname = function(path) {
350349

351350

352351
win32.basename = function(path, ext) {
353-
var f = win32.splitPath(path)[2];
352+
var f = win32SplitPath(path)[2];
354353
// TODO: make this comparison case-insensitive on windows?
355354
if (ext && f.substr(-1 * ext.length) === ext) {
356355
f = f.substr(0, f.length - ext.length);
@@ -360,7 +359,57 @@ win32.basename = function(path, ext) {
360359

361360

362361
win32.extname = function(path) {
363-
return win32.splitPath(path)[3];
362+
return win32SplitPath(path)[3];
363+
};
364+
365+
366+
win32.format = function(pathObject) {
367+
if (!util.isObject(pathObject)) {
368+
throw new TypeError(
369+
"Parameter 'pathObject' must be an object, not " + typeof pathObject
370+
);
371+
}
372+
373+
var root = pathObject.root || '';
374+
375+
if (!util.isString(root)) {
376+
throw new TypeError(
377+
"'pathObject.root' must be a string or undefined, not " +
378+
typeof pathObject.root
379+
);
380+
}
381+
382+
var dir = pathObject.dir;
383+
var base = pathObject.base || '';
384+
if (dir.slice(dir.length - 1, dir.length) === win32.sep) {
385+
return dir + base;
386+
}
387+
388+
if (dir) {
389+
return dir + win32.sep + base;
390+
}
391+
392+
return base;
393+
};
394+
395+
396+
win32.parse = function(pathString) {
397+
if (!util.isString(pathString)) {
398+
throw new TypeError(
399+
"Parameter 'pathString' must be a string, not " + typeof pathString
400+
);
401+
}
402+
var allParts = win32SplitPath(pathString);
403+
if (!allParts || allParts.length !== 4) {
404+
throw new TypeError("Invalid path '" + pathString + "'");
405+
}
406+
return {
407+
root: allParts[0],
408+
dir: allParts[0] + allParts[1].slice(0, allParts[1].length - 1),
409+
base: allParts[2],
410+
ext: allParts[3],
411+
name: allParts[2].slice(0, allParts[2].length - allParts[3].length)
412+
};
364413
};
365414

366415

@@ -375,9 +424,9 @@ var splitPathRe =
375424
var posix = {};
376425

377426

378-
posix.splitPath = function(filename) {
427+
function posixSplitPath(filename) {
379428
return splitPathRe.exec(filename).slice(1);
380-
};
429+
}
381430

382431

383432
// path.resolve([from ...], to)
@@ -512,7 +561,7 @@ posix._makeLong = function(path) {
512561

513562

514563
posix.dirname = function(path) {
515-
var result = posix.splitPath(path),
564+
var result = posixSplitPath(path),
516565
root = result[0],
517566
dir = result[1];
518567

@@ -531,7 +580,7 @@ posix.dirname = function(path) {
531580

532581

533582
posix.basename = function(path, ext) {
534-
var f = posix.splitPath(path)[2];
583+
var f = posixSplitPath(path)[2];
535584
// TODO: make this comparison case-insensitive on windows?
536585
if (ext && f.substr(-1 * ext.length) === ext) {
537586
f = f.substr(0, f.length - ext.length);
@@ -541,7 +590,53 @@ posix.basename = function(path, ext) {
541590

542591

543592
posix.extname = function(path) {
544-
return posix.splitPath(path)[3];
593+
return posixSplitPath(path)[3];
594+
};
595+
596+
597+
posix.format = function(pathObject) {
598+
if (!util.isObject(pathObject)) {
599+
throw new TypeError(
600+
"Parameter 'pathObject' must be an object, not " + typeof pathObject
601+
);
602+
}
603+
604+
var root = pathObject.root || '';
605+
606+
if (!util.isString(root)) {
607+
throw new TypeError(
608+
"'pathObject.root' must be a string or undefined, not " +
609+
typeof pathObject.root
610+
);
611+
}
612+
613+
var dir = pathObject.dir ? pathObject.dir + posix.sep : '';
614+
var base = pathObject.base || '';
615+
return dir + base;
616+
};
617+
618+
619+
posix.parse = function(pathString) {
620+
if (!util.isString(pathString)) {
621+
throw new TypeError(
622+
"Parameter 'pathString' must be a string, not " + typeof pathString
623+
);
624+
}
625+
var allParts = posixSplitPath(pathString);
626+
if (!allParts || allParts.length !== 4) {
627+
throw new TypeError("Invalid path '" + pathString + "'");
628+
}
629+
allParts[1] = allParts[1] || '';
630+
allParts[2] = allParts[2] || '';
631+
allParts[3] = allParts[3] || '';
632+
633+
return {
634+
root: allParts[0],
635+
dir: allParts[0] + allParts[1].slice(0, allParts[1].length - 1),
636+
base: allParts[2],
637+
ext: allParts[3],
638+
name: allParts[2].slice(0, allParts[2].length - allParts[3].length)
639+
};
545640
};
546641

547642

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
// Copyright Joyent, Inc. and other Node contributors.
2+
//
3+
// Permission is hereby granted, free of charge, to any person obtaining a
4+
// copy of this software and associated documentation files (the
5+
// "Software"), to deal in the Software without restriction, including
6+
// without limitation the rights to use, copy, modify, merge, publish,
7+
// distribute, sublicense, and/or sell copies of the Software, and to permit
8+
// persons to whom the Software is furnished to do so, subject to the
9+
// following conditions:
10+
//
11+
// The above copyright notice and this permission notice shall be included
12+
// in all copies or substantial portions of the Software.
13+
//
14+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15+
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16+
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
17+
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
18+
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
19+
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
20+
// USE OR OTHER DEALINGS IN THE SOFTWARE.
21+
22+
var assert = require('assert');
23+
var path = require('path');
24+
25+
var winPaths = [
26+
'C:\\path\\dir\\index.html',
27+
'C:\\another_path\\DIR\\1\\2\\33\\index',
28+
'another_path\\DIR with spaces\\1\\2\\33\\index',
29+
'\\foo\\C:',
30+
'file',
31+
'.\\file',
32+
33+
// unc
34+
'\\\\server\\share\\file_path',
35+
'\\\\server two\\shared folder\\file path.zip',
36+
'\\\\teela\\admin$\\system32',
37+
'\\\\?\\UNC\\server\\share'
38+
39+
];
40+
41+
var unixPaths = [
42+
'/home/user/dir/file.txt',
43+
'/home/user/a dir/another File.zip',
44+
'/home/user/a dir//another&File.',
45+
'/home/user/a$$$dir//another File.zip',
46+
'user/dir/another File.zip',
47+
'file',
48+
'.\\file',
49+
'./file',
50+
'C:\\foo'
51+
];
52+
53+
var errors = [
54+
{method: 'parse', input: [null], message: /Parameter 'pathString' must be a string, not/},
55+
{method: 'parse', input: [{}], message: /Parameter 'pathString' must be a string, not object/},
56+
{method: 'parse', input: [true], message: /Parameter 'pathString' must be a string, not boolean/},
57+
{method: 'parse', input: [1], message: /Parameter 'pathString' must be a string, not number/},
58+
{method: 'parse', input: [], message: /Parameter 'pathString' must be a string, not undefined/},
59+
// {method: 'parse', input: [''], message: /Invalid path/}, // omitted because it's hard to trigger!
60+
{method: 'format', input: [null], message: /Parameter 'pathObject' must be an object, not/},
61+
{method: 'format', input: [''], message: /Parameter 'pathObject' must be an object, not string/},
62+
{method: 'format', input: [true], message: /Parameter 'pathObject' must be an object, not boolean/},
63+
{method: 'format', input: [1], message: /Parameter 'pathObject' must be an object, not number/},
64+
{method: 'format', input: [{root: true}], message: /'pathObject.root' must be a string or undefined, not boolean/},
65+
{method: 'format', input: [{root: 12}], message: /'pathObject.root' must be a string or undefined, not number/},
66+
];
67+
68+
check(path.win32, winPaths);
69+
check(path.posix, unixPaths);
70+
checkErrors(path.win32);
71+
checkErrors(path.posix);
72+
73+
function checkErrors(path) {
74+
errors.forEach(function(errorCase) {
75+
try {
76+
path[errorCase.method].apply(path, errorCase.input);
77+
} catch(err) {
78+
assert.ok(err instanceof TypeError);
79+
assert.ok(
80+
errorCase.message.test(err.message),
81+
'expected ' + errorCase.message + ' to match ' + err.message
82+
);
83+
return;
84+
}
85+
86+
assert.fail('should have thrown');
87+
});
88+
}
89+
90+
91+
function check(path, paths) {
92+
paths.forEach(function(element, index, array) {
93+
var output = path.parse(element);
94+
assert.strictEqual(path.format(output), element);
95+
assert.strictEqual(output.dir, output.dir ? path.dirname(element) : '');
96+
assert.strictEqual(output.base, path.basename(element));
97+
assert.strictEqual(output.ext, path.extname(element));
98+
});
99+
}

0 commit comments

Comments
 (0)