Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 20 additions & 6 deletions src/crawler.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { type ExcludePredicate, type FSLike, fdir } from 'fdir';
import picomatch, { type PicomatchOptions } from 'picomatch';
import processPatterns from './patterns.ts';
import type { Crawler, InternalOptions, InternalProps, RelativeMapper } from './types.ts';
import type { Crawler, CrawlerInfo, InternalOptions, InternalProps, RelativeMapper } from './types.ts';
import { BACKSLASHES, buildFormat, buildRelative, getPartialMatcher, log } from './utils.ts';

// #region buildCrawler
export function buildCrawler(options: InternalOptions, patterns: readonly string[]): [Crawler, false | RelativeMapper] {
// #region buildCrawlerInfo
export function buildCrawlerInfo(options: InternalOptions, patterns: readonly string[]): CrawlerInfo {
const cwd = options.cwd as string;
const props: InternalProps = { root: cwd, depthOffset: 0 };
const processed = processPatterns(options, patterns, props);
Expand All @@ -14,7 +14,7 @@ export function buildCrawler(options: InternalOptions, patterns: readonly string
log('internal processing patterns:', processed);
}

const { absolute, caseSensitiveMatch, debug, dot, followSymbolicLinks, onlyDirectories } = options;
const { absolute, caseSensitiveMatch, dot } = options;
const root = props.root.replace(BACKSLASHES, '');
// For some of these options, false and undefined are two different states!
const matchOptions = {
Expand All @@ -26,11 +26,25 @@ export function buildCrawler(options: InternalOptions, patterns: readonly string
posix: true
} satisfies PicomatchOptions;

const format = buildFormat(cwd, root, absolute);

return { processed, matchOptions, cwd, root, absolute, props, format, patterns };
}
// #endregion buildCrawlerInfo

// #region buildCrawler
export function buildCrawler(
options: InternalOptions,
patterns: readonly string[]
): [Crawler, false | RelativeMapper, CrawlerInfo] {
const info = buildCrawlerInfo(options, patterns);
const { processed, matchOptions, cwd, root, absolute, props, format } = info;
const { debug, followSymbolicLinks, onlyDirectories } = options;

const matcher = picomatch(processed.match, { ...matchOptions, ignore: processed.ignore });
const ignore = picomatch(processed.ignore, matchOptions);
const partialMatcher = getPartialMatcher(processed.match, matchOptions);

const format = buildFormat(cwd, root, absolute);
const excludeFormatter = absolute ? format : buildFormat(cwd, root, true);

const excludePredicate: ExcludePredicate = (_, p): boolean => {
Expand Down Expand Up @@ -83,6 +97,6 @@ export function buildCrawler(options: InternalOptions, patterns: readonly string
log('internal properties:', { ...props, root });
}

return [crawler, cwd !== root && !absolute && buildRelative(cwd, root)];
return [crawler, cwd !== root && !absolute && buildRelative(cwd, root), info];
}
// #endregion buildCrawler
86 changes: 37 additions & 49 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import nativeFs from 'node:fs';
import { resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import { buildCrawler } from './crawler.ts';
import type { Crawler, FileSystemAdapter, GlobInput, GlobOptions, InternalOptions, RelativeMapper } from './types.ts';
import { BACKSLASHES, ensureStringArray, isReadonlyArray, log } from './utils.ts';
import { getOptions } from './options.ts';
import { internalSortFiles } from './sort.ts';
import type { Crawler, CrawlerInfo, GlobInput, GlobOptions, RelativeMapper } from './types.ts';
import { ensureStringArray, isReadonlyArray } from './utils.ts';

function formatPaths(paths: string[], mapper?: false | RelativeMapper) {
if (mapper) {
Expand All @@ -14,54 +13,34 @@ function formatPaths(paths: string[], mapper?: false | RelativeMapper) {
return paths;
}

const fsKeys = ['readdir', 'readdirSync', 'realpath', 'realpathSync', 'stat', 'statSync'];

function normalizeFs(fs?: Record<string, unknown>): FileSystemAdapter | undefined {
if (fs && fs !== nativeFs) {
for (const key of fsKeys) {
fs[key] = (fs[key] ? fs : (nativeFs as Record<string, unknown>))[key];
}
}
return fs;
}

// Object containing all default options to ensure there is no hidden state difference
// between false and undefined.
const defaultOptions: GlobOptions = {
caseSensitiveMatch: true,
cwd: process.cwd(),
debug: !!process.env.TINYGLOBBY_DEBUG,
expandDirectories: true,
followSymbolicLinks: true,
onlyFiles: true
};

function getOptions(options?: GlobOptions): InternalOptions {
const opts = { ...defaultOptions, ...options } as InternalOptions;

opts.cwd = (opts.cwd instanceof URL ? fileURLToPath(opts.cwd) : resolve(opts.cwd)).replace(BACKSLASHES, '/');
// Default value of [] will be inserted here if ignore is undefined
opts.ignore = ensureStringArray(opts.ignore);
opts.fs = normalizeFs(opts.fs);

if (opts.debug) {
log('globbing with options:', opts);
}

return opts;
}

function getCrawler(globInput: GlobInput, inputOptions: GlobOptions = {}): [] | [Crawler, false | RelativeMapper] {
function getCrawler(
globInput: GlobInput,
inputOptions: GlobOptions = {}
): [] | [Crawler, false | RelativeMapper, GlobOptions, readonly string[], CrawlerInfo] {
if (globInput && inputOptions?.patterns) {
throw new Error('Cannot pass patterns as both an argument and an option');
}

const isModern = isReadonlyArray(globInput) || typeof globInput === 'string';
// defaulting to ['**/*'] is tinyglobby exclusive behavior, deprecated
const patterns = ensureStringArray((isModern ? globInput : globInput.patterns) ?? '**/*');

if (patterns.length === 0) {
return [];
}

const options = getOptions(isModern ? inputOptions : globInput);
const [crawler, relative, crawlerInfo] = buildCrawler(options, patterns);
return [crawler, relative, options, patterns, crawlerInfo];
}

return patterns.length > 0 ? buildCrawler(options, patterns) : [];
function processResults(
paths: string[],
relative: false | RelativeMapper,
options: GlobOptions,
crawlerInfo: CrawlerInfo
): string[] {
return internalSortFiles(formatPaths(paths, relative), crawlerInfo, options.sort);
}

/**
Expand All @@ -74,8 +53,12 @@ export function glob(patterns: string | readonly string[], options?: Omit<GlobOp
*/
export function glob(options: GlobOptions): Promise<string[]>;
export async function glob(globInput: GlobInput, options?: GlobOptions): Promise<string[]> {
const [crawler, relative] = getCrawler(globInput, options);
return crawler ? formatPaths(await crawler.withPromise(), relative) : [];
const result = getCrawler(globInput, options);
if (result.length === 0) {
return [];
}
const [crawler, relative, opts, _, crawlerInfo] = result;
return processResults(await crawler.withPromise(), relative, opts, crawlerInfo);
}

/**
Expand All @@ -88,9 +71,14 @@ export function globSync(patterns: string | readonly string[], options?: Omit<Gl
*/
export function globSync(options: GlobOptions): string[];
export function globSync(globInput: GlobInput, options?: GlobOptions): string[] {
const [crawler, relative] = getCrawler(globInput, options);
return crawler ? formatPaths(crawler.sync(), relative) : [];
const result = getCrawler(globInput, options);
if (result.length === 0) {
return [];
}
const [crawler, relative, opts, _, crawlerInfo] = result;
return processResults(crawler.sync(), relative, opts, crawlerInfo);
}

export type { GlobOptions } from './types.ts';
export { compileMatchers, sortFiles, sortFilesByPatternPrecedence } from './sort.ts';
export type { GlobOptions, Sort } from './types.ts';
export { convertPathToPattern, escapePath, isDynamicPattern } from './utils.ts';
42 changes: 42 additions & 0 deletions src/options.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import nativeFs from 'node:fs';
import { resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import type { FileSystemAdapter, GlobOptions, InternalOptions } from './types.ts';
import { BACKSLASHES, ensureStringArray, log } from './utils.ts';

const fsKeys = ['readdir', 'readdirSync', 'realpath', 'realpathSync', 'stat', 'statSync'];

function normalizeFs(fs?: Record<string, unknown>): FileSystemAdapter | undefined {
if (fs && fs !== nativeFs) {
for (const key of fsKeys) {
fs[key] = (fs[key] ? fs : (nativeFs as Record<string, unknown>))[key];
}
}
return fs;
}

// Object containing all default options to ensure there is no hidden state difference
// between false and undefined.
const defaultOptions: GlobOptions = {
caseSensitiveMatch: true,
cwd: process.cwd(),
debug: !!process.env.TINYGLOBBY_DEBUG,
expandDirectories: true,
followSymbolicLinks: true,
onlyFiles: true
};

export function getOptions(options?: GlobOptions): InternalOptions {
const opts = { ...defaultOptions, ...options } as InternalOptions;

opts.cwd = (opts.cwd instanceof URL ? fileURLToPath(opts.cwd) : resolve(opts.cwd)).replace(BACKSLASHES, '/');
// Default value of [] will be inserted here if ignore is undefined
opts.ignore = ensureStringArray(opts.ignore);
opts.fs = normalizeFs(opts.fs);

if (opts.debug) {
log('globbing with options:', opts);
}

return opts;
}
Loading
Loading