UNPKG

abi.js

Version:

[![typescript-icon]][typescript-link] [![license-icon]][license-link] [![status-icon]][status-link] [![ci-icon]][ci-link] [![twitter-icon]][twitter-link]

413 lines (412 loc) 13 kB
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. import * as dntShim from "../../../../../_dnt.shims.js"; import { globToRegExp } from "../../path/1.0.2/glob_to_regexp.js"; import { joinGlobs } from "../../path/1.0.2/join_globs.js"; import { isGlob } from "../../path/1.0.2/is_glob.js"; import { isAbsolute } from "../../path/1.0.2/is_absolute.js"; import { resolve } from "../../path/1.0.2/resolve.js"; import { SEPARATOR_PATTERN } from "../../path/1.0.2/constants.js"; import { walk, walkSync } from "./walk.js"; import { toPathString } from "./_to_path_string.js"; import { createWalkEntry, createWalkEntrySync, } from "./_create_walk_entry.js"; const isWindows = dntShim.Deno.build.os === "windows"; function split(path) { const s = SEPARATOR_PATTERN.source; const segments = path .replace(new RegExp(`^${s}|${s}$`, "g"), "") .split(SEPARATOR_PATTERN); const isAbsolute_ = isAbsolute(path); return { segments, isAbsolute: isAbsolute_, hasTrailingSep: !!path.match(new RegExp(`${s}$`)), winRoot: isWindows && isAbsolute_ ? segments.shift() : undefined, }; } function throwUnlessNotFound(error) { if (!(error instanceof dntShim.Deno.errors.NotFound)) { throw error; } } function comparePath(a, b) { if (a.path < b.path) return -1; if (a.path > b.path) return 1; return 0; } /** * Returns an async iterator that yields each file path matching the given glob * pattern. * * The file paths are absolute paths. If `root` is not provided, the current * working directory is used. The `root` directory is not included in the * yielded file paths. * * Requires `--allow-read` permission. * * @see {@link https://docs.deno.com/runtime/manual/basics/permissions#file-system-access} * for more information on Deno's permissions system. * * @param glob The glob pattern to expand. * @param options Additional options for the expansion. * * @returns An async iterator that yields each walk entry matching the glob * pattern. * * @example Basic usage * * File structure: * ``` * folder * ├── script.ts * └── foo.ts * ``` * * ```ts no-eval * // script.ts * import { expandGlob } from "@std/fs/expand-glob"; * * await Array.fromAsync(expandGlob("*.ts")); * // [ * // { * // path: "/Users/user/folder/script.ts", * // name: "script.ts", * // isFile: true, * // isDirectory: false, * // isSymlink: false, * // }, * // { * // path: "/Users/user/folder/foo.ts", * // name: "foo.ts", * // isFile: true, * // isDirectory: false, * // isSymlink: false, * // }, * // ] * ``` * * @example Define root directory * * Setting the `root` option to `/folder` will expand the glob pattern from the * `/folder` directory. * * File structure: * ``` * folder * ├── subdir * │ └── bar.ts * ├── script.ts * └── foo.ts * ``` * * ```ts no-eval * // script.ts * import { expandGlob } from "@std/fs/expand-glob"; * * await Array.fromAsync(expandGlob("*.ts", { root: "./subdir" })); * // [ * // { * // path: "/Users/user/folder/subdir/bar.ts", * // name: "bar.ts", * // isFile: true, * // isDirectory: false, * // isSymlink: false, * // }, * // ] * ``` * * @example Exclude files * * Setting the `exclude` option to `["foo.ts"]` will exclude the `foo.ts` file * from the expansion. * * File structure: * ``` * folder * ├── script.ts * └── foo.ts * ``` * * ```ts no-eval * // script.ts * import { expandGlob } from "@std/fs/expand-glob"; * * await Array.fromAsync(expandGlob("*.ts", { exclude: ["foo.ts"] })); * // [ * // { * // path: "/Users/user/folder/script.ts", * // name: "true.ts", * // isFile: false, * // isDirectory: false, * // isSymlink: false, * // }, * // ] * ``` * * @example Exclude directories * * Setting the `includeDirs` option to `false` will exclude directories from the * expansion. * * File structure: * ``` * folder * ├── subdir * │ └── bar.ts * ├── script.ts * └── foo.ts * ``` * * ```ts no-eval * // script.ts * import { expandGlob } from "@std/fs/expand-glob"; * * await Array.fromAsync(expandGlob("*", { includeDirs: false })); * // [ * // { * // path: "/Users/user/folder/script.ts", * // name: "script.ts", * // isFile: true, * // isDirectory: false, * // isSymlink: false, * // }, * // { * // path: "/Users/user/folder/foo.ts", * // name: "foo.ts", * // isFile: true, * // isDirectory: false, * // isSymlink: false, * // }, * // ] * ``` * * @example Follow symbolic links * * Setting the `followSymlinks` option to `true` will follow symbolic links. * * File structure: * ``` * folder * ├── script.ts * └── link.ts -> script.ts (symbolic link) * ``` * * ```ts no-eval * // script.ts * import { expandGlob } from "@std/fs/expand-glob"; * * await Array.fromAsync(expandGlob("*.ts", { followSymlinks: true })); * // [ * // { * // path: "/Users/user/folder/script.ts", * // name: "script.ts", * // isFile: true, * // isDirectory: false, * // isSymlink: false, * // }, * // { * // path: "/Users/user/folder/symlink", * // name: "symlink", * // isFile: true, * // isDirectory: false, * // isSymlink: true, * // }, * // ] * ``` */ export async function* expandGlob(glob, options) { let { root, exclude = [], includeDirs = true, extended = true, globstar = true, caseInsensitive, followSymlinks, canonicalize, } = options ?? {}; const { segments, isAbsolute: isGlobAbsolute, hasTrailingSep, winRoot, } = split(toPathString(glob)); root ??= isGlobAbsolute ? winRoot ?? "/" : dntShim.Deno.cwd(); const globOptions = { extended, globstar, caseInsensitive }; const absRoot = isGlobAbsolute ? root : resolve(root); // root is always string here const resolveFromRoot = (path) => resolve(absRoot, path); const excludePatterns = exclude .map(resolveFromRoot) .map((s) => globToRegExp(s, globOptions)); const shouldInclude = (path) => !excludePatterns.some((p) => !!path.match(p)); let fixedRoot = isGlobAbsolute ? winRoot ?? "/" : absRoot; while (segments.length > 0 && !isGlob(segments[0])) { const seg = segments.shift(); fixedRoot = joinGlobs([fixedRoot, seg], globOptions); } let fixedRootInfo; try { fixedRootInfo = await createWalkEntry(fixedRoot); } catch (error) { return throwUnlessNotFound(error); } async function* advanceMatch(walkInfo, globSegment) { if (!walkInfo.isDirectory) { return; } else if (globSegment === "..") { const parentPath = joinGlobs([walkInfo.path, ".."], globOptions); if (shouldInclude(parentPath)) { return yield await createWalkEntry(parentPath); } return; } else if (globSegment === "**") { return yield* walk(walkInfo.path, { skip: excludePatterns, maxDepth: globstar ? Infinity : 1, followSymlinks, canonicalize, }); } const globPattern = globToRegExp(globSegment, globOptions); for await (const walkEntry of walk(walkInfo.path, { maxDepth: 1, skip: excludePatterns, followSymlinks, })) { if (walkEntry.path !== walkInfo.path && walkEntry.name.match(globPattern)) { yield walkEntry; } } } let currentMatches = [fixedRootInfo]; for (const segment of segments) { // Advancing the list of current matches may introduce duplicates, so we // pass everything through this Map. const nextMatchMap = new Map(); await Promise.all(currentMatches.map(async (currentMatch) => { for await (const nextMatch of advanceMatch(currentMatch, segment)) { nextMatchMap.set(nextMatch.path, nextMatch); } })); currentMatches = [...nextMatchMap.values()].sort(comparePath); } if (hasTrailingSep) { currentMatches = currentMatches.filter((entry) => entry.isDirectory); } if (!includeDirs) { currentMatches = currentMatches.filter((entry) => !entry.isDirectory); } yield* currentMatches; } /** * Returns an iterator that yields each file path matching the given glob * pattern. The file paths are relative to the provided `root` directory. * If `root` is not provided, the current working directory is used. * The `root` directory is not included in the yielded file paths. * * Requires the `--allow-read` flag. * * @see {@link https://docs.deno.com/runtime/manual/basics/permissions#file-system-access} * for more information on Deno's permissions system. * * @param glob The glob pattern to expand. * @param options Additional options for the expansion. * * @returns An iterator that yields each walk entry matching the glob pattern. * * @example Usage * * File structure: * ``` * folder * ├── script.ts * └── foo.ts * ``` * * ```ts no-eval * // script.ts * import { expandGlobSync } from "@std/fs/expand-glob"; * * const entries = []; * for (const entry of expandGlobSync("*.ts")) { * entries.push(entry); * } * * entries[0]!.path; // "/Users/user/folder/script.ts" * entries[0]!.name; // "script.ts" * entries[0]!.isFile; // false * entries[0]!.isDirectory; // true * entries[0]!.isSymlink; // false * * entries[1]!.path; // "/Users/user/folder/foo.ts" * entries[1]!.name; // "foo.ts" * entries[1]!.isFile; // true * entries[1]!.isDirectory; // false * entries[1]!.isSymlink; // false * ``` */ export function* expandGlobSync(glob, options) { let { root, exclude = [], includeDirs = true, extended = true, globstar = true, caseInsensitive, followSymlinks, canonicalize, } = options ?? {}; const { segments, isAbsolute: isGlobAbsolute, hasTrailingSep, winRoot, } = split(toPathString(glob)); root ??= isGlobAbsolute ? winRoot ?? "/" : dntShim.Deno.cwd(); const globOptions = { extended, globstar, caseInsensitive }; const absRoot = isGlobAbsolute ? root : resolve(root); // root is always string here const resolveFromRoot = (path) => resolve(absRoot, path); const excludePatterns = exclude .map(resolveFromRoot) .map((s) => globToRegExp(s, globOptions)); const shouldInclude = (path) => !excludePatterns.some((p) => !!path.match(p)); let fixedRoot = isGlobAbsolute ? winRoot ?? "/" : absRoot; while (segments.length > 0 && !isGlob(segments[0])) { const seg = segments.shift(); fixedRoot = joinGlobs([fixedRoot, seg], globOptions); } let fixedRootInfo; try { fixedRootInfo = createWalkEntrySync(fixedRoot); } catch (error) { return throwUnlessNotFound(error); } function* advanceMatch(walkInfo, globSegment) { if (!walkInfo.isDirectory) { return; } else if (globSegment === "..") { const parentPath = joinGlobs([walkInfo.path, ".."], globOptions); if (shouldInclude(parentPath)) { return yield createWalkEntrySync(parentPath); } return; } else if (globSegment === "**") { return yield* walkSync(walkInfo.path, { skip: excludePatterns, maxDepth: globstar ? Infinity : 1, followSymlinks, canonicalize, }); } const globPattern = globToRegExp(globSegment, globOptions); for (const walkEntry of walkSync(walkInfo.path, { maxDepth: 1, skip: excludePatterns, followSymlinks, })) { if (walkEntry.path !== walkInfo.path && walkEntry.name.match(globPattern)) { yield walkEntry; } } } let currentMatches = [fixedRootInfo]; for (const segment of segments) { // Advancing the list of current matches may introduce duplicates, so we // pass everything through this Map. const nextMatchMap = new Map(); for (const currentMatch of currentMatches) { for (const nextMatch of advanceMatch(currentMatch, segment)) { nextMatchMap.set(nextMatch.path, nextMatch); } } currentMatches = [...nextMatchMap.values()].sort(comparePath); } if (hasTrailingSep) { currentMatches = currentMatches.filter((entry) => entry.isDirectory); } if (!includeDirs) { currentMatches = currentMatches.filter((entry) => !entry.isDirectory); } yield* currentMatches; }