l0bsterssg

node.js static responsive blog post generator
Log | Files | Refs | README

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;