From 08573426fdba9147558049beb7990c43bed6b67a Mon Sep 17 00:00:00 2001 From: Rory Bradford Date: Tue, 8 Apr 2014 22:48:24 +0100 Subject: [PATCH] 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. --- doc/api/path.markdown | 42 ++++++++++++++++ lib/path.js | 69 ++++++++++++++++++++++----- test/simple/test-path-parse-format.js | 64 +++++++++++++++++++++++++ 3 files changed, 164 insertions(+), 11 deletions(-) create mode 100644 test/simple/test-path-parse-format.js diff --git a/doc/api/path.markdown b/doc/api/path.markdown index 054159533af7..c4468713abc8 100644 --- a/doc/api/path.markdown +++ b/doc/api/path.markdown @@ -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 diff --git a/lib/path.js b/lib/path.js index 2cab82acd5e2..f89e3f498ff6 100644 --- a/lib/path.js +++ b/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,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 || ''; + 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); + 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 +398,9 @@ var splitPathRe = var posix = {}; -posix.splitPath = function(filename) { +function posixSplitPath(filename) { return splitPathRe.exec(filename).slice(1); -}; +} // path.resolve([from ...], to) @@ -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]; @@ -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); @@ -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 || ''; + var sep = posix.sep; + if (root.indexOf(':') > -1) { + sep = '\\'; + } + var dir = object.dir + sep; + var base = object.base || ''; + return dir + base; +}; + + +posix.parse = function(string) { + var allParts = posixSplitPath(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) + }; }; diff --git a/test/simple/test-path-parse-format.js b/test/simple/test-path-parse-format.js new file mode 100644 index 000000000000..e5759d07408c --- /dev/null +++ b/test/simple/test-path-parse-format.js @@ -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' + +]; + +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' +]; + +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]]); + } + + 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)); + }) +}