All files / src/fs/util path-util.js

100% Statements 57/57
100% Branches 26/26
100% Functions 12/12
100% Lines 51/51

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165                1x 21x               1x 687x 13x   674x               1x 704x                         1x 679x 125x     554x 554x   896x     448x     106x               1x 320x     160x     23x                   981x 387x   387x 60x         327x 279x   279x                   1x 33x 3x     30x 30x   30x               1x 27x   27x               1x 3x                       1x 1x 31x   390x 130x 130x   243x 243x 31x 22x   212x 195x       130x    
/**
 * Tests if a path is a trailing path.
 *
 * A trailing path ends with a trailing slash (/) and excludes the root
 * directory (/).
 * @param  {string}  path path with or without a trailing slash
 * @return {Boolean}      true, if the path is a trailing path
 */
export const isTrailingPath = (path) => {
  return path.endsWith('/') && path !== '/';
};
 
/**
 * Removes a trailing slash (/) from a path
 * @param  {string} path path with or without a trailing /
 * @return {string}      path without trailing /
 */
export const removeTrailingSeparator = (path) => {
  if (path.endsWith('/') && path !== '/') {
    return path.slice(0, -1);
  }
  return path;
};
 
/**
 * Tests if a path is absolute
 * @param  {string}  path
 * @return {boolean}
 */
export const isAbsPath = (path) => {
  return path.startsWith('/');
};
 
/**
 * Converts a path to an ordered array of folders and files.
 *
 * Example: Parts of '/a/b/c/e.txt' has parts of ['/', 'a', 'b', 'c', 'e.txt']
 *
 * A relative path splits parts at /. An absolute path splits at / and also
 * considers the root directory (/) as a part of the path.
 * @param  {string} path [description]
 * @return {array}       list of path parts
 */
export const toPathParts = (path) => {
  if (path === '/') {
    return ['/'];
  };
 
  path = removeTrailingSeparator(path);
  const pathParts = path.split('/');
 
  if (isAbsPath(path)) {
    const [, ...nonRootPathParts] = pathParts;
 
    return ['/', ...nonRootPathParts];
  }
 
  return pathParts;
};
 
/**
 * Converts path parts back to a path
 * @param  {array} pathParts path parts
 * @return {string}          path
 */
export const toPath = (pathParts) => {
  if (pathParts[0] === '/') { // absolute path
    const [, ...nonRootPathParts] = pathParts;
 
    return `/${nonRootPathParts.join('/')}`;
  }
 
  return pathParts.join('/');
};
 
/**
 * Find breadcrumb paths, i.e. all paths that need to be walked to get to
 * the specified path
 * Example: /a/b/c will have breadcrumb paths of '/', '/a', '/a/b', '/a/b/c'
 * @param  {string} path path to a directory
 * @return {array}       list of paths that lead up to a path
 */
export const getPathBreadCrumbs = (path) => {
  const pathParts = toPathParts(path);
 
  if (pathParts.length <= 1) {
    return ['/'];
  }
 
  const [, secondPathPart, ...pathPartsWithoutRoot] = pathParts;
 
  return pathPartsWithoutRoot.reduce((breadCrumbs, pathPart) => {
    const previousBreadCrumb = breadCrumbs[breadCrumbs.length - 1];
 
    return [...breadCrumbs, `${previousBreadCrumb}/${pathPart}`];
  }, ['/', `/${secondPathPart}`]);
};
 
/**
 * Removes the file name from the end of a file path, returning the path to the
 * directory of the file
 * @param  {string} filePath path which ends with a file name
 * @return {string}          directory path
 */
export const getPathParent = (filePath) => {
  if (filePath === '/') {
    return '/';
  }
 
  const pathParts = toPathParts(filePath); // converts path string to array
  const pathPartsWithoutFileName = pathParts.slice(0, -1); // removes last element of array
 
  return toPath(pathPartsWithoutFileName);
};
 
/**
 * Extracts the file name from the end of the file path
 * @param  {string} filePath path which ends with a file name
 * @return {string}          file name from the path
 */
export const getLastPathPart = (filePath) => {
  const pathParts = toPathParts(filePath); // converts path string to array
 
  return pathParts[pathParts.length - 1];
};
 
/**
 * Extracts the file name and directory path from a file path
 * @param  {string} filePath path which ends with a file name
 * @return {object}          object with directory and file name
 */
export const splitFilePath = (filePath) => {
  return {
    'dirPath': getPathParent(filePath),
    'fileName': getLastPathPart(filePath)
  };
};
 
/**
 * Converts a relative path to an absolute path
 * @param  {string} relativePath
 * @param  {string} cwd          current working directory
 * @return {string}              absolute path
 */
const GO_UP = '..';
const CURRENT_DIR = '.';
const isStackAtRootDirectory = stack => stack.length === 1 && stack[0] === '/';
 
export const toAbsolutePath = (relativePath, cwd) => {
  relativePath = removeTrailingSeparator(relativePath);
  const pathStack = isAbsPath(relativePath) ? [] : toPathParts(cwd);
 
  for (const pathPart of toPathParts(relativePath)) {
    if (pathPart === GO_UP) {
      if (!isStackAtRootDirectory(pathStack)) {
        pathStack.pop();
      }
    } else if (pathPart !== CURRENT_DIR) {
      pathStack.push(pathPart);
    }
  }
 
  return toPath(pathStack);
};