import { path } from "./deps/path.ts";import { GlobError } from "./errors.ts";import { getPath, normalize, normalizeDirectory, removeUndefined,} from "./utils.ts";import { createMatcher, CreateMatcherOptions, Match, MatchFunction,} from "./utils/create_matcher.ts";
export interface GlobProps extends CreateMatcherOptions, BaseGlobProps { cwd: string | URL;
include?: Match | Match[];
exclude?: Match | Match[];}
export async function* glob(options: GlobProps) { const props = { ...DEFAULT_OPTIONS, ...removeUndefined(options) }; const cwd = getPath(props.cwd); const includer = createMatcher(props.include, props); const excluder = createMatcher(props.exclude, props); const extra = { cwd, includer, excluder }; const directory = normalizeDirectory(cwd);
yield* walkDirectory(directory, props.maxDepth, { ...props, ...extra });}
const DEFAULT_OPTIONS: Required<GlobProps> = { include: ["**/*"], exclude: [], extensions: null, cwd: Deno.cwd(), maxDepth: Number.POSITIVE_INFINITY, includeFiles: true, includeDirectories: true, followSymlinks: true, dot: false, junk: false, expandGlobs: true, disableNegation: false, emptyDirectories: false, trailingSlash: true, caseInsensitive: false, concurrent: false,};
async function createGlobEntry( absolute: string, cwd: string, trailingSlash?: boolean,): Promise<GlobEntry> { absolute = normalize(absolute); const { isDirectory, isFile, isSymlink } = await Deno.stat(absolute); absolute = isDirectory ? normalizeDirectory(absolute, trailingSlash) : absolute; const name = isDirectory ? normalizeDirectory(path.basename(absolute), trailingSlash) : path.basename(absolute); const relative = isDirectory ? normalizeDirectory(path.relative(cwd, absolute), trailingSlash) : path.relative(cwd, absolute);
return { absolute, relative, name, isFile, isDirectory, isSymlink };}
interface ShouldExcludeProps { path: string; includer?: MatchFunction; excluder?: MatchFunction;}
function shouldInclude(props: ShouldExcludeProps): boolean { if (props.excluder?.(props.path) === true) { return false; }
return props.includer?.(props.path) !== false;}
export interface GlobEntry extends Deno.DirEntry { name: string; absolute: string; relative: string;}
interface WalkDirectoryOptions extends Required<BaseGlobProps> { includer: MatchFunction; excluder: MatchFunction; cwd: string;}
async function* walkDirectory( root: string, depth: number, options: WalkDirectoryOptions,): AsyncGenerator<GlobEntry, void> { if (depth < 0) { return; }
const relativeDirectory = path.relative(options.cwd, root);
const includeProps = { excluder: options.excluder, includer: options.includer, };
if ( options.includeDirectories && shouldInclude({ path: relativeDirectory, ...includeProps }) ) { yield await createGlobEntry(root, options.cwd, options.trailingSlash); }
if ( depth < 1 || !shouldInclude({ path: root, excluder: options.excluder }) ) { return; }
try { for await (const entry of Deno.readDir(root)) { let resolvedRoot = path.resolve(root, entry.name); let relativeRoot = path.relative(options.cwd, resolvedRoot); let { isSymlink, isDirectory } = entry;
if (isSymlink) { if (!options.followSymlinks) { continue; }
resolvedRoot = await Deno.realPath(resolvedRoot); relativeRoot = path.relative(options.cwd, resolvedRoot); ({ isSymlink, isDirectory } = await Deno.lstat(resolvedRoot)); }
if (isSymlink || isDirectory) { yield* walkDirectory(resolvedRoot, depth - 1, options); } else if ( options.includeFiles && shouldInclude({ path: relativeRoot, ...includeProps }) ) { yield { name: path.basename(resolvedRoot), absolute: resolvedRoot, relative: relativeRoot, isDirectory: false, isSymlink: false, isFile: true, }; } } } catch (error) { throw GlobError.wrap(error, root); }}
interface BaseGlobProps { maxDepth?: number;
followSymlinks?: boolean;
includeDirectories?: boolean;
includeFiles?: boolean;
trailingSlash?: boolean;
emptyDirectories?: boolean;
concurrent?: boolean;}