MartyO256/find-files-by-patterns

View on GitHub
src/directories.ts

Summary

Maintainability
B
4 hrs
Test Coverage
import { dirname, parse, resolve } from "path";

import {
  downwardFiles,
  downwardFilesSync,
  handleDownwardFilesOverload,
} from "./files.js";
import { filter, filterSync } from "./filter.js";
import { isDirectory, isDirectorySync } from "./stat.js";

/**
 * Constructs an iterable over the downward directories starting from the
 * current working directory. Symbolic links are followed, and the directories
 * are traversed in breadth-first order. Directories are read only once. The
 * current working directory is not yielded.
 * @returns An iterable over the downward directories.
 */
export function downwardDirectories(): AsyncIterable<string>;

/**
 * Constructs an iterable over the downward directories starting from the
 * current working directory and down to a given maximum depth of a directory.
 * Symbolic links are followed, and the directories are traversed in
 * breadth-first order. Directories are read only once. The start directory is
 * not yielded.
 * @param maximumDepth The maximum depth of a read directory relative to the
 * start directory. This maximum depth should be zero or positive.
 * @throws If the maximum depth is negative.
 * @returns An iterable over the downward directories down to the maximum
 * depth.
 */
export function downwardDirectories(
  maximumDepth: number,
): AsyncIterable<string>;

/**
 * Constructs an iterable over the downward directories starting from a given
 * path. Symbolic links are followed, and the directories are traversed in
 * breadth-first order. Directories are read only once. The start directory is
 * not yielded.
 * @param startDirectory The start directory from which to start the downward
 * traversal.
 * @throws If the start path is a file.
 * @throws If the start path is inexistant.
 * @returns An iterable over the downward directories.
 */
export function downwardDirectories(
  startDirectory: string,
): AsyncIterable<string>;

/**
 * Constructs an iterable over the downward directories starting from a given
 * path and down to a given maximum depth of a directory. Symbolic links are
 * followed, and the directories are traversed in breadth-first order.
 * Directories are read only once. The start directory is not yielded.
 * @param startDirectory The start directory from which to start the downward
 * traversal.
 * @param maximumDepth The maximum depth of a read directory relative to the
 * start directory. This maximum depth should be zero or positive.
 * @throws If the start path is a file.
 * @throws If the start path is inexistant.
 * @throws If the maximum depth is negative.
 * @returns An iterable over the downward directories down to the maximum
 * depth.
 */
export function downwardDirectories(
  startDirectory: string,
  maximumDepth: number,
): AsyncIterable<string>;

/**
 * A downward directories fetcher constructs an iterable over the directories
 * downwards from a given directory path.
 */
export function downwardDirectories(
  startDirectory?: number | string,
  maximumDepth?: number,
): AsyncIterable<string> {
  [startDirectory, maximumDepth] = handleDownwardFilesOverload(
    startDirectory,
    maximumDepth,
  );
  return filter(downwardFiles(startDirectory, maximumDepth), isDirectory);
}

/**
 * Constructs an iterable over the downward directories starting from the
 * current working directory. Symbolic links are followed, and the directories
 * are traversed in breadth-first order. Directories are read only once. The
 * start directory is not yielded.
 * @returns An iterable over the downward directories.
 */
export function downwardDirectoriesSync(): Iterable<string>;

/**
 * Constructs an iterable over the downward directories starting from the
 * current working directory and down to a given maximum depth of a directory.
 * Symbolic links are followed, and the directories are traversed in
 * breadth-first order. Directories are read only once. The start directory is
 * not yielded.
 * @param maximumDepth The maximum depth of a read directory relative to the
 * start directory. This maximum depth should be zero or positive.
 * @throws If the maximum depth is negative.
 * @returns An iterable over the downward directories down to the maximum
 * depth.
 */
export function downwardDirectoriesSync(maximumDepth: number): Iterable<string>;

/**
 * Constructs an iterable over the downward directories starting from a given
 * path. Symbolic links are followed, and the directories are traversed in
 * breadth-first order. Directories are read only once. The start directory is
 * not yielded.
 * @param startDirectory The start directory from which to start the downward
 * traversal.
 * @throws If the start path is a file.
 * @throws If the start path is inexistant.
 * @returns An iterable over the downward directories.
 */
export function downwardDirectoriesSync(
  startDirectory: string,
): Iterable<string>;

/**
 * Constructs an iterable over the downward directories starting from a given
 * path and down to a given maximum depth of a directory. Symbolic links are
 * followed, and the directories are traversed in breadth-first order.
 * Directories are read only once. The start directory is not yielded.
 * @param startDirectory The start directory from which to start the downward
 * traversal.
 * @param maximumDepth The maximum depth of a read directory relative to the
 * start directory. This maximum depth should be zero or positive.
 * @throws If the start path is a file.
 * @throws If the start path is inexistant.
 * @throws If the maximum depth is negative.
 * @returns An iterable over the downward directories down to the maximum
 * depth.
 */
export function downwardDirectoriesSync(
  startDirectory: string,
  maximumDepth: number,
): Iterable<string>;

/**
 * A downward directories fetcher constructs an iterable over the directories
 * downwards from a given directory path.
 */
export function downwardDirectoriesSync(
  startDirectory?: number | string,
  maximumDepth?: number,
): Iterable<string> {
  [startDirectory, maximumDepth] = handleDownwardFilesOverload(
    startDirectory,
    maximumDepth,
  );
  return filterSync(
    downwardFilesSync(startDirectory, maximumDepth),
    isDirectorySync,
  );
}

/**
 * Returns an iterable over the upward paths from a given start path up to the
 * root. The start path may be a file, a directory, or be inexistant. The
 * yielded paths may not exist on the file system. The start path is not
 * yielded. The paths are yielded in increasing order of height relative to the
 * start path.
 * @param startPath The start path from which to iterate upward.
 * @returns The iterable over the upward paths starting from the given path.
 */
export function* upwardPaths(startPath: string): Iterable<string> {
  let upwardDirectory = startPath;
  const { root } = parse(upwardDirectory);
  do {
    upwardDirectory = dirname(upwardDirectory);
    yield upwardDirectory;
  } while (upwardDirectory !== root);
}

/**
 * Returns an iterable over the upward paths from a given start path up to a
 * maximum path height in the file system graph. The path at the maximum depth
 * is yielded. The start path may be a file, a directory, or be inexistant. The
 * yielded paths may not exist on the file system. The start path is not
 * yielded. The paths are yielded in increasing order of height relative to the
 * start path.
 * @param startPath The start path from which to iterate upward.
 * @param maximumHeight The maximum height of any yielded path relative to the
 * start path. If it is zero or negative, then no paths are yielded.
 * @returns The iterable over the upward paths starting from the given path and
 * up to the given maximum height.
 */
export function* upwardConstrainedPaths(
  startPath: string,
  maximumHeight: number,
): Iterable<string> {
  let height = 1;
  for (const upwardPath of upwardPaths(startPath)) {
    if (height <= maximumHeight) {
      yield upwardPath;
      height++;
    } else {
      break;
    }
  }
}

/**
 * Returns an iterable over the upward paths from a given start path up to a
 * limit path. End path is yielded if it is reached. The start and end paths may
 * be files, directories, or be inexistant. The yielded paths may not exist on
 * the file system. The start path is not yielded. The paths are yielded in
 * increasing order of height relative to the start path. If the start and end
 * paths do not share the same root, then the end path won't be yielded as it
 * cannot be reached upwards from the start path.
 * @param startPath The start path from which to iterate upward.
 * @param endPath The limit path at which to end the iteration if it is ever
 * reached.
 * @returns The iterable over the upward paths starting from the given path and
 * up to the given end path.
 */
export function* upwardLimitedPaths(
  startPath: string,
  endPath: string,
): Iterable<string> {
  for (const upwardPath of upwardPaths(startPath)) {
    yield upwardPath;
    if (endPath === upwardPath) {
      break;
    }
  }
}

/**
 * Returns an upward paths iterable with either a string or number path, each
 * corresponding to an end path and a maximum path height respectively.
 * @see [[upwardPaths]] The unbounded upward paths iterable.
 * @see [[upwardConstrainedPaths]] The upward paths iterable bounded by a
 * maximum path height.
 * @see [[upwardLimitedPaths]] The upward paths iterable bounded by limit path.
 * @param startPath The start path of the iteration.
 * @param upperBound The upper bound on the iterable.
 * @returns A bounded or unbounded upward paths iterable based on the given
 * upper bound.
 */
const overloadedUpwardPaths = (
  startPath: string,
  upperBound?: number | string,
): Iterable<string> => {
  startPath = resolve(startPath);
  if (upperBound === undefined) {
    return upwardPaths(startPath);
  } else if (typeof upperBound === "string") {
    upperBound = resolve(upperBound);
  }
  return typeof upperBound === "number"
    ? upwardConstrainedPaths(startPath, upperBound)
    : upwardLimitedPaths(startPath, upperBound);
};

/**
 * Constructs an iterable over the upward directories starting from the
 * current working directory. The current working directory is not yielded.
 * The directories are yielded in ascending order of height relative to the
 * current working directory.
 * @returns An iterable over the upward directories.
 */
export function upwardDirectories(): AsyncIterable<string>;

/**
 * Constructs an iterable over the upward directories starting from the given
 * path. The start path is not yielded. If the start path is a file, then its
 * directory is yielded. The directories are yielded in ascending order of
 * height relative to the given start path.
 * @param startPath The start path from which to traverse upwards.
 * @returns An iterable over the upward directories.
 */
export function upwardDirectories(startPath: string): AsyncIterable<string>;

/**
 * Constructs an iterable over the upward directories starting from the given
 * path and up to a given maximum height. The start path is not yielded. If
 * the start path is a file, then its directory is yielded. The directories
 * are yielded in ascending order of height relative to the given start path.
 * Each yielded directory has a height greater than one, and less than or
 * equal to the given maximum height.
 * @param startPath The start path from which to traverse upwards.
 * @param maximumHeight The maximum height of any yielded directory path.
 * @returns An iterable over the upward directories.
 */
export function upwardDirectories(
  startPath: string,
  maximumHeight: number,
): AsyncIterable<string>;

/**
 * Constructs an iterable over the upward directories starting from the given
 * path and up to a given end path. The start path is not yielded. If the
 * start path is a file, then its directory is yielded. The directories are
 * yielded in ascending order of height relative to the given start path. If
 * the end path is not parent to the start path, then the iteration ends with
 * the root of the start path. The iteration has to encounter the end path in
 * order to end once it is yielded.
 * @param startPath The start path from which to traverse upwards.
 * @param endPath The path on which the iteration will end if it is
 * encountered in the upward traversal.
 * @returns An iterable over the upward directories.
 */
export function upwardDirectories(
  startPath: string,
  endPath: string,
): AsyncIterable<string>;

/**
 * An upward directories fetcher constructs an iterable over the directories
 * upwards from a given directory path.
 */
export function upwardDirectories(
  startPath = ".",
  upperBound?: number | string,
): AsyncIterable<string> {
  return filter(overloadedUpwardPaths(startPath, upperBound), isDirectory);
}

/**
 * Constructs an iterable over the upward directories starting from the
 * current working directory. The current working directory is not yielded.
 * The directories are yielded in ascending order of height relative to the
 * current working directory.
 * @returns An iterable over the upward directories.
 */
export function upwardDirectoriesSync(): Iterable<string>;

/**
 * Constructs an iterable over the upward directories starting from the given
 * path. The start path is not yielded. If the start path is a file, then its
 * directory is yielded. The directories are yielded in ascending order of
 * height relative to the given start path.
 * @param startPath The start path from which to traverse upwards.
 * @returns An iterable over the upward directories.
 */
export function upwardDirectoriesSync(startPath: string): Iterable<string>;

/**
 * Constructs an iterable over the upward directories starting from the given
 * path and up to a given maximum height. The start path is not yielded. If
 * the start path is a file, then its directory is yielded. The directories
 * are yielded in ascending order of height relative to the given start path.
 * Each yielded directory has a height greater than one, and less than or
 * equal to the given maximum height.
 * @param startPath The start path from which to traverse upwards.
 * @param maximumHeight The maximum height of any yielded directory path.
 * @returns An iterable over the upward directories.
 */
export function upwardDirectoriesSync(
  startPath: string,
  maximumHeight: number,
): Iterable<string>;

/**
 * Constructs an iterable over the upward directories starting from the given
 * path and up to a given end path. The start path is not yielded. If the
 * start path is a file, then its directory is yielded. The directories are
 * yielded in ascending order of height relative to the given start path. If
 * the end path is not parent to the start path, then the iteration ends with
 * the root of the start path. The iteration has to encounter the end path in
 * order to end once it is yielded.
 * @param startPath The start path from which to traverse upwards.
 * @param endPath The path on which the iteration will end if it is
 * encountered in the upward traversal.
 * @returns An iterable over the upward directories.
 */
export function upwardDirectoriesSync(
  startPath: string,
  endPath: string,
): Iterable<string>;

/**
 * An upward directories fetcher constructs an iterable over the directories
 * upwards from a given directory path.
 */
export function upwardDirectoriesSync(
  startPath = ".",
  upperBound?: number | string,
): Iterable<string> {
  return filterSync(
    overloadedUpwardPaths(startPath, upperBound),
    isDirectorySync,
  );
}