path.js (17060B)
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 'use strict'; 23 24 25 var isWindows = process.platform === 'win32'; 26 var util = require('util'); 27 28 29 // resolves . and .. elements in a path array with directory names there 30 // must be no slashes or device names (c:\) in the array 31 // (so also no leading and trailing slashes - it does not distinguish 32 // relative and absolute paths) 33 function normalizeArray(parts, allowAboveRoot) { 34 var res = []; 35 for (var i = 0; i < parts.length; i++) { 36 var p = parts[i]; 37 38 // ignore empty parts 39 if (!p || p === '.') 40 continue; 41 42 if (p === '..') { 43 if (res.length && res[res.length - 1] !== '..') { 44 res.pop(); 45 } else if (allowAboveRoot) { 46 res.push('..'); 47 } 48 } else { 49 res.push(p); 50 } 51 } 52 53 return res; 54 } 55 56 // returns an array with empty elements removed from either end of the input 57 // array or the original array if no elements need to be removed 58 function trimArray(arr) { 59 var lastIndex = arr.length - 1; 60 var start = 0; 61 for (; start <= lastIndex; start++) { 62 if (arr[start]) 63 break; 64 } 65 66 var end = lastIndex; 67 for (; end >= 0; end--) { 68 if (arr[end]) 69 break; 70 } 71 72 if (start === 0 && end === lastIndex) 73 return arr; 74 if (start > end) 75 return []; 76 return arr.slice(start, end + 1); 77 } 78 79 // Regex to split a windows path into three parts: [*, device, slash, 80 // tail] windows-only 81 var splitDeviceRe = 82 /^([a-zA-Z]:|[\\\/]{2}[^\\\/]+[\\\/]+[^\\\/]+)?([\\\/])?([\s\S]*?)$/; 83 84 // Regex to split the tail part of the above into [*, dir, basename, ext] 85 var splitTailRe = 86 /^([\s\S]*?)((?:\.{1,2}|[^\\\/]+?|)(\.[^.\/\\]*|))(?:[\\\/]*)$/; 87 88 var win32 = {}; 89 90 // Function to split a filename into [root, dir, basename, ext] 91 function win32SplitPath(filename) { 92 // Separate device+slash from tail 93 var result = splitDeviceRe.exec(filename), 94 device = (result[1] || '') + (result[2] || ''), 95 tail = result[3] || ''; 96 // Split the tail into dir, basename and extension 97 var result2 = splitTailRe.exec(tail), 98 dir = result2[1], 99 basename = result2[2], 100 ext = result2[3]; 101 return [device, dir, basename, ext]; 102 } 103 104 function win32StatPath(path) { 105 var result = splitDeviceRe.exec(path), 106 device = result[1] || '', 107 isUnc = !!device && device[1] !== ':'; 108 return { 109 device: device, 110 isUnc: isUnc, 111 isAbsolute: isUnc || !!result[2], // UNC paths are always absolute 112 tail: result[3] 113 }; 114 } 115 116 function normalizeUNCRoot(device) { 117 return '\\\\' + device.replace(/^[\\\/]+/, '').replace(/[\\\/]+/g, '\\'); 118 } 119 120 // path.resolve([from ...], to) 121 win32.resolve = function() { 122 var resolvedDevice = '', 123 resolvedTail = '', 124 resolvedAbsolute = false; 125 126 for (var i = arguments.length - 1; i >= -1; i--) { 127 var path; 128 if (i >= 0) { 129 path = arguments[i]; 130 } else if (!resolvedDevice) { 131 path = process.cwd(); 132 } else { 133 // Windows has the concept of drive-specific current working 134 // directories. If we've resolved a drive letter but not yet an 135 // absolute path, get cwd for that drive. We're sure the device is not 136 // an unc path at this points, because unc paths are always absolute. 137 path = process.env['=' + resolvedDevice]; 138 // Verify that a drive-local cwd was found and that it actually points 139 // to our drive. If not, default to the drive's root. 140 if (!path || path.substr(0, 3).toLowerCase() !== 141 resolvedDevice.toLowerCase() + '\\') { 142 path = resolvedDevice + '\\'; 143 } 144 } 145 146 // Skip empty and invalid entries 147 if (!util.isString(path)) { 148 throw new TypeError('Arguments to path.resolve must be strings'); 149 } else if (!path) { 150 continue; 151 } 152 153 var result = win32StatPath(path), 154 device = result.device, 155 isUnc = result.isUnc, 156 isAbsolute = result.isAbsolute, 157 tail = result.tail; 158 159 if (device && 160 resolvedDevice && 161 device.toLowerCase() !== resolvedDevice.toLowerCase()) { 162 // This path points to another device so it is not applicable 163 continue; 164 } 165 166 if (!resolvedDevice) { 167 resolvedDevice = device; 168 } 169 if (!resolvedAbsolute) { 170 resolvedTail = tail + '\\' + resolvedTail; 171 resolvedAbsolute = isAbsolute; 172 } 173 174 if (resolvedDevice && resolvedAbsolute) { 175 break; 176 } 177 } 178 179 // Convert slashes to backslashes when `resolvedDevice` points to an UNC 180 // root. Also squash multiple slashes into a single one where appropriate. 181 if (isUnc) { 182 resolvedDevice = normalizeUNCRoot(resolvedDevice); 183 } 184 185 // At this point the path should be resolved to a full absolute path, 186 // but handle relative paths to be safe (might happen when process.cwd() 187 // fails) 188 189 // Normalize the tail path 190 resolvedTail = normalizeArray(resolvedTail.split(/[\\\/]+/), 191 !resolvedAbsolute).join('\\'); 192 193 return (resolvedDevice + (resolvedAbsolute ? '\\' : '') + resolvedTail) || 194 '.'; 195 }; 196 197 198 win32.normalize = function(path) { 199 var result = win32StatPath(path), 200 device = result.device, 201 isUnc = result.isUnc, 202 isAbsolute = result.isAbsolute, 203 tail = result.tail, 204 trailingSlash = /[\\\/]$/.test(tail); 205 206 // Normalize the tail path 207 tail = normalizeArray(tail.split(/[\\\/]+/), !isAbsolute).join('\\'); 208 209 if (!tail && !isAbsolute) { 210 tail = '.'; 211 } 212 if (tail && trailingSlash) { 213 tail += '\\'; 214 } 215 216 // Convert slashes to backslashes when `device` points to an UNC root. 217 // Also squash multiple slashes into a single one where appropriate. 218 if (isUnc) { 219 device = normalizeUNCRoot(device); 220 } 221 222 return device + (isAbsolute ? '\\' : '') + tail; 223 }; 224 225 226 win32.isAbsolute = function(path) { 227 return win32StatPath(path).isAbsolute; 228 }; 229 230 win32.join = function() { 231 var paths = []; 232 for (var i = 0; i < arguments.length; i++) { 233 var arg = arguments[i]; 234 if (!util.isString(arg)) { 235 throw new TypeError('Arguments to path.join must be strings'); 236 } 237 if (arg) { 238 paths.push(arg); 239 } 240 } 241 242 var joined = paths.join('\\'); 243 244 // Make sure that the joined path doesn't start with two slashes, because 245 // normalize() will mistake it for an UNC path then. 246 // 247 // This step is skipped when it is very clear that the user actually 248 // intended to point at an UNC path. This is assumed when the first 249 // non-empty string arguments starts with exactly two slashes followed by 250 // at least one more non-slash character. 251 // 252 // Note that for normalize() to treat a path as an UNC path it needs to 253 // have at least 2 components, so we don't filter for that here. 254 // This means that the user can use join to construct UNC paths from 255 // a server name and a share name; for example: 256 // path.join('//server', 'share') -> '\\\\server\\share\') 257 if (!/^[\\\/]{2}[^\\\/]/.test(paths[0])) { 258 joined = joined.replace(/^[\\\/]{2,}/, '\\'); 259 } 260 261 return win32.normalize(joined); 262 }; 263 264 265 // path.relative(from, to) 266 // it will solve the relative path from 'from' to 'to', for instance: 267 // from = 'C:\\orandea\\test\\aaa' 268 // to = 'C:\\orandea\\impl\\bbb' 269 // The output of the function should be: '..\\..\\impl\\bbb' 270 win32.relative = function(from, to) { 271 from = win32.resolve(from); 272 to = win32.resolve(to); 273 274 // windows is not case sensitive 275 var lowerFrom = from.toLowerCase(); 276 var lowerTo = to.toLowerCase(); 277 278 var toParts = trimArray(to.split('\\')); 279 280 var lowerFromParts = trimArray(lowerFrom.split('\\')); 281 var lowerToParts = trimArray(lowerTo.split('\\')); 282 283 var length = Math.min(lowerFromParts.length, lowerToParts.length); 284 var samePartsLength = length; 285 for (var i = 0; i < length; i++) { 286 if (lowerFromParts[i] !== lowerToParts[i]) { 287 samePartsLength = i; 288 break; 289 } 290 } 291 292 if (samePartsLength == 0) { 293 return to; 294 } 295 296 var outputParts = []; 297 for (var i = samePartsLength; i < lowerFromParts.length; i++) { 298 outputParts.push('..'); 299 } 300 301 outputParts = outputParts.concat(toParts.slice(samePartsLength)); 302 303 return outputParts.join('\\'); 304 }; 305 306 307 win32._makeLong = function(path) { 308 // Note: this will *probably* throw somewhere. 309 if (!util.isString(path)) 310 return path; 311 312 if (!path) { 313 return ''; 314 } 315 316 var resolvedPath = win32.resolve(path); 317 318 if (/^[a-zA-Z]\:\\/.test(resolvedPath)) { 319 // path is local filesystem path, which needs to be converted 320 // to long UNC path. 321 return '\\\\?\\' + resolvedPath; 322 } else if (/^\\\\[^?.]/.test(resolvedPath)) { 323 // path is network UNC path, which needs to be converted 324 // to long UNC path. 325 return '\\\\?\\UNC\\' + resolvedPath.substring(2); 326 } 327 328 return path; 329 }; 330 331 332 win32.dirname = function(path) { 333 var result = win32SplitPath(path), 334 root = result[0], 335 dir = result[1]; 336 337 if (!root && !dir) { 338 // No dirname whatsoever 339 return '.'; 340 } 341 342 if (dir) { 343 // It has a dirname, strip trailing slash 344 dir = dir.substr(0, dir.length - 1); 345 } 346 347 return root + dir; 348 }; 349 350 351 win32.basename = function(path, ext) { 352 var f = win32SplitPath(path)[2]; 353 // TODO: make this comparison case-insensitive on windows? 354 if (ext && f.substr(-1 * ext.length) === ext) { 355 f = f.substr(0, f.length - ext.length); 356 } 357 return f; 358 }; 359 360 361 win32.extname = function(path) { 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) { 385 return base; 386 } 387 if (dir[dir.length - 1] === win32.sep) { 388 return dir + base; 389 } 390 return dir + win32.sep + base; 391 }; 392 393 394 win32.parse = function(pathString) { 395 if (!util.isString(pathString)) { 396 throw new TypeError( 397 "Parameter 'pathString' must be a string, not " + typeof pathString 398 ); 399 } 400 var allParts = win32SplitPath(pathString); 401 if (!allParts || allParts.length !== 4) { 402 throw new TypeError("Invalid path '" + pathString + "'"); 403 } 404 return { 405 root: allParts[0], 406 dir: allParts[0] + allParts[1].slice(0, -1), 407 base: allParts[2], 408 ext: allParts[3], 409 name: allParts[2].slice(0, allParts[2].length - allParts[3].length) 410 }; 411 }; 412 413 414 win32.sep = '\\'; 415 win32.delimiter = ';'; 416 417 418 // Split a filename into [root, dir, basename, ext], unix version 419 // 'root' is just a slash, or nothing. 420 var splitPathRe = 421 /^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/; 422 var posix = {}; 423 424 425 function posixSplitPath(filename) { 426 return splitPathRe.exec(filename).slice(1); 427 } 428 429 430 // path.resolve([from ...], to) 431 // posix version 432 posix.resolve = function() { 433 var resolvedPath = '', 434 resolvedAbsolute = false; 435 436 for (var i = arguments.length - 1; i >= -1 && !resolvedAbsolute; i--) { 437 var path = (i >= 0) ? arguments[i] : process.cwd(); 438 439 // Skip empty and invalid entries 440 if (!util.isString(path)) { 441 throw new TypeError('Arguments to path.resolve must be strings'); 442 } else if (!path) { 443 continue; 444 } 445 446 resolvedPath = path + '/' + resolvedPath; 447 resolvedAbsolute = path[0] === '/'; 448 } 449 450 // At this point the path should be resolved to a full absolute path, but 451 // handle relative paths to be safe (might happen when process.cwd() fails) 452 453 // Normalize the path 454 resolvedPath = normalizeArray(resolvedPath.split('/'), 455 !resolvedAbsolute).join('/'); 456 457 return ((resolvedAbsolute ? '/' : '') + resolvedPath) || '.'; 458 }; 459 460 // path.normalize(path) 461 // posix version 462 posix.normalize = function(path) { 463 var isAbsolute = posix.isAbsolute(path), 464 trailingSlash = path && path[path.length - 1] === '/'; 465 466 // Normalize the path 467 path = normalizeArray(path.split('/'), !isAbsolute).join('/'); 468 469 if (!path && !isAbsolute) { 470 path = '.'; 471 } 472 if (path && trailingSlash) { 473 path += '/'; 474 } 475 476 return (isAbsolute ? '/' : '') + path; 477 }; 478 479 // posix version 480 posix.isAbsolute = function(path) { 481 return path.charAt(0) === '/'; 482 }; 483 484 // posix version 485 posix.join = function() { 486 var path = ''; 487 for (var i = 0; i < arguments.length; i++) { 488 var segment = arguments[i]; 489 if (!util.isString(segment)) { 490 throw new TypeError('Arguments to path.join must be strings'); 491 } 492 if (segment) { 493 if (!path) { 494 path += segment; 495 } else { 496 path += '/' + segment; 497 } 498 } 499 } 500 return posix.normalize(path); 501 }; 502 503 504 // path.relative(from, to) 505 // posix version 506 posix.relative = function(from, to) { 507 from = posix.resolve(from).substr(1); 508 to = posix.resolve(to).substr(1); 509 510 var fromParts = trimArray(from.split('/')); 511 var toParts = trimArray(to.split('/')); 512 513 var length = Math.min(fromParts.length, toParts.length); 514 var samePartsLength = length; 515 for (var i = 0; i < length; i++) { 516 if (fromParts[i] !== toParts[i]) { 517 samePartsLength = i; 518 break; 519 } 520 } 521 522 var outputParts = []; 523 for (var i = samePartsLength; i < fromParts.length; i++) { 524 outputParts.push('..'); 525 } 526 527 outputParts = outputParts.concat(toParts.slice(samePartsLength)); 528 529 return outputParts.join('/'); 530 }; 531 532 533 posix._makeLong = function(path) { 534 return path; 535 }; 536 537 538 posix.dirname = function(path) { 539 var result = posixSplitPath(path), 540 root = result[0], 541 dir = result[1]; 542 543 if (!root && !dir) { 544 // No dirname whatsoever 545 return '.'; 546 } 547 548 if (dir) { 549 // It has a dirname, strip trailing slash 550 dir = dir.substr(0, dir.length - 1); 551 } 552 553 return root + dir; 554 }; 555 556 557 posix.basename = function(path, ext) { 558 var f = posixSplitPath(path)[2]; 559 // TODO: make this comparison case-insensitive on windows? 560 if (ext && f.substr(-1 * ext.length) === ext) { 561 f = f.substr(0, f.length - ext.length); 562 } 563 return f; 564 }; 565 566 567 posix.extname = function(path) { 568 return posixSplitPath(path)[3]; 569 }; 570 571 572 posix.format = function(pathObject) { 573 if (!util.isObject(pathObject)) { 574 throw new TypeError( 575 "Parameter 'pathObject' must be an object, not " + typeof pathObject 576 ); 577 } 578 579 var root = pathObject.root || ''; 580 581 if (!util.isString(root)) { 582 throw new TypeError( 583 "'pathObject.root' must be a string or undefined, not " + 584 typeof pathObject.root 585 ); 586 } 587 588 var dir = pathObject.dir ? pathObject.dir + posix.sep : ''; 589 var base = pathObject.base || ''; 590 return dir + base; 591 }; 592 593 594 posix.parse = function(pathString) { 595 if (!util.isString(pathString)) { 596 throw new TypeError( 597 "Parameter 'pathString' must be a string, not " + typeof pathString 598 ); 599 } 600 var allParts = posixSplitPath(pathString); 601 if (!allParts || allParts.length !== 4) { 602 throw new TypeError("Invalid path '" + pathString + "'"); 603 } 604 allParts[1] = allParts[1] || ''; 605 allParts[2] = allParts[2] || ''; 606 allParts[3] = allParts[3] || ''; 607 608 return { 609 root: allParts[0], 610 dir: allParts[0] + allParts[1].slice(0, -1), 611 base: allParts[2], 612 ext: allParts[3], 613 name: allParts[2].slice(0, allParts[2].length - allParts[3].length) 614 }; 615 }; 616 617 618 posix.sep = '/'; 619 posix.delimiter = ':'; 620 621 622 if (isWindows) 623 module.exports = win32; 624 else /* posix */ 625 module.exports = posix; 626 627 module.exports.posix = posix; 628 module.exports.win32 = win32;