path.js 17 KB

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