Skip to content

Commit

Permalink
Expose new generateGlobTasks and generateGlobTasksSync (#221)
Browse files Browse the repository at this point in the history
  • Loading branch information
fisker committed Jan 19, 2022
1 parent 93f83f3 commit 51c8f68
Show file tree
Hide file tree
Showing 9 changed files with 230 additions and 95 deletions.
12 changes: 11 additions & 1 deletion index.d.ts
Expand Up @@ -4,7 +4,7 @@ import {Options as FastGlobOptions, Entry} from 'fast-glob';
export type GlobEntry = Entry;

export interface GlobTask {
readonly pattern: string;
readonly patterns: string[];
readonly options: Options;
}

Expand Down Expand Up @@ -141,6 +141,16 @@ Note that you should avoid running the same tasks multiple times as they contain
export function generateGlobTasks(
patterns: string | readonly string[],
options?: Options
): Promise<GlobTask[]>;

/**
@see generateGlobTasks
@returns An object in the format `{pattern: string, options: object}`, which can be passed as arguments to [`fast-glob`](https://github.com/mrmlnc/fast-glob). This is useful for other globbing-related packages.
*/
export function generateGlobTasksSync(
patterns: string | readonly string[],
options?: Options
): GlobTask[];

/**
Expand Down
50 changes: 24 additions & 26 deletions index.js
Expand Up @@ -49,7 +49,8 @@ const normalizeOptions = (options = {}) => {
return options;
};

const normalizeArguments = fn => (patterns, options) => fn(toPatternsArray(patterns), normalizeOptions(options));
const normalizeArguments = fn => async (patterns, options) => fn(toPatternsArray(patterns), normalizeOptions(options));
const normalizeArgumentsSync = fn => (patterns, options) => fn(toPatternsArray(patterns), normalizeOptions(options));

const getFilter = async options => createFilterFunction(
options.gitignore && await isGitIgnored({cwd: options.cwd, ignore: options.ignore}),
Expand All @@ -71,7 +72,7 @@ const createFilterFunction = isIgnored => {
const unionFastGlobResults = (results, filter) => results.flat().filter(fastGlobResult => filter(fastGlobResult));
const unionFastGlobStreams = (streams, filter) => merge2(streams).pipe(new FilterStream(fastGlobResult => filter(fastGlobResult)));

const generateGlobTasksInternal = (patterns, taskOptions) => {
const convertNegativePatterns = (patterns, taskOptions) => {
const globTasks = [];
for (const [index, pattern] of patterns.entries()) {
if (isNegative(pattern)) {
Expand All @@ -88,7 +89,7 @@ const generateGlobTasksInternal = (patterns, taskOptions) => {
ignore: [...taskOptions.ignore, ...ignore],
};

globTasks.push({pattern, options});
globTasks.push({patterns: [pattern], options});
}

return globTasks;
Expand All @@ -100,7 +101,7 @@ const getDirGlobOptions = (options, cwd) => ({
});

const generateTasks = async (patterns, options) => {
const globTasks = generateGlobTasksInternal(patterns, options);
const globTasks = convertNegativePatterns(patterns, options);

const {cwd, expandDirectories} = options;

Expand All @@ -113,24 +114,23 @@ const generateTasks = async (patterns, options) => {

return Promise.all(
globTasks.map(async task => {
const {pattern, options} = task;
let {patterns, options} = task;

const [
[
patterns,
ignore,
options.ignore,
] = await Promise.all([
dirGlob(pattern, patternExpandOptions),
dirGlob(patterns, patternExpandOptions),
dirGlob(options.ignore, ignoreExpandOptions),
]);

options.ignore = ignore;
return {pattern: patterns, options};
return {patterns, options};
}),
);
};

const generateTasksSync = (patterns, options) => {
const globTasks = generateGlobTasksInternal(patterns, options);
const globTasks = convertNegativePatterns(patterns, options);

const {cwd, expandDirectories} = options;

Expand All @@ -142,50 +142,48 @@ const generateTasksSync = (patterns, options) => {
const ignoreExpandOptions = cwd ? {cwd} : undefined;

return globTasks.map(task => {
const {pattern, options} = task;
const patterns = dirGlob.sync(pattern, patternExpandOptions);
let {patterns, options} = task;
patterns = dirGlob.sync(patterns, patternExpandOptions);
options.ignore = dirGlob.sync(options.ignore, ignoreExpandOptions);
return {pattern: patterns, options};
return {patterns, options};
});
};

export const globby = async (patterns, options) => {
patterns = toPatternsArray(patterns);
options = normalizeOptions(options);

export const globby = normalizeArguments(async (patterns, options) => {
const [
tasks,
filter,
] = await Promise.all([
generateTasks(patterns, options),
getFilter(options),
]);
const results = await Promise.all(tasks.map(task => fastGlob(task.pattern, task.options)));
const results = await Promise.all(tasks.map(task => fastGlob(task.patterns, task.options)));

return unionFastGlobResults(results, filter);
};
});

export const globbySync = normalizeArguments((patterns, options) => {
export const globbySync = normalizeArgumentsSync((patterns, options) => {
const tasks = generateTasksSync(patterns, options);
const filter = getFilterSync(options);
const results = tasks.map(task => fastGlob.sync(task.pattern, task.options));
const results = tasks.map(task => fastGlob.sync(task.patterns, task.options));

return unionFastGlobResults(results, filter);
});

export const globbyStream = normalizeArguments((patterns, options) => {
export const globbyStream = normalizeArgumentsSync((patterns, options) => {
const tasks = generateTasksSync(patterns, options);
const filter = getFilterSync(options);
const streams = tasks.map(task => fastGlob.stream(task.pattern, task.options));
const streams = tasks.map(task => fastGlob.stream(task.patterns, task.options));

return unionFastGlobStreams(streams, filter);
});

export const isDynamicPattern = normalizeArguments(
export const isDynamicPattern = normalizeArgumentsSync(
(patterns, options) => patterns.some(pattern => fastGlob.isDynamicPattern(pattern, options)),
);

export const generateGlobTasks = normalizeArguments(generateGlobTasksInternal);
export const generateGlobTasks = normalizeArguments(generateTasks);
export const generateGlobTasksSync = normalizeArgumentsSync(generateTasksSync);

export {
isGitIgnored,
Expand Down
34 changes: 27 additions & 7 deletions index.test-d.ts
Expand Up @@ -9,6 +9,7 @@ import {
globbySync,
globbyStream,
generateGlobTasks,
generateGlobTasksSync,
isDynamicPattern,
isGitIgnored,
isGitIgnoredSync,
Expand Down Expand Up @@ -83,23 +84,42 @@ expectType<NodeJS.ReadableStream>(globbyStream('*.tmp', {ignore: ['**/b.tmp']}))
})();

// GenerateGlobTasks
expectType<GlobTask[]>(generateGlobTasks('*.tmp'));
expectType<GlobTask[]>(generateGlobTasks(['a.tmp', '*.tmp', '!{c,d,e}.tmp']));
expectType<Promise<GlobTask[]>>(generateGlobTasks('*.tmp'));
expectType<Promise<GlobTask[]>>(generateGlobTasks(['a.tmp', '*.tmp', '!{c,d,e}.tmp']));

expectType<GlobTask[]>(generateGlobTasks('*.tmp', {expandDirectories: false}));
expectType<GlobTask[]>(
expectType<Promise<GlobTask[]>>(generateGlobTasks('*.tmp', {expandDirectories: false}));
expectType<Promise<GlobTask[]>>(
generateGlobTasks('*.tmp', {expandDirectories: ['a*', 'b*']}),
);
expectType<GlobTask[]>(
expectType<Promise<GlobTask[]>>(
generateGlobTasks('*.tmp', {
expandDirectories: {
files: ['a', 'b'],
extensions: ['tmp'],
},
}),
);
expectType<GlobTask[]>(generateGlobTasks('*.tmp', {gitignore: true}));
expectType<GlobTask[]>(generateGlobTasks('*.tmp', {ignore: ['**/b.tmp']}));
expectType<Promise<GlobTask[]>>(generateGlobTasks('*.tmp', {gitignore: true}));
expectType<Promise<GlobTask[]>>(generateGlobTasks('*.tmp', {ignore: ['**/b.tmp']}));

// GenerateGlobTasksSync
expectType<GlobTask[]>(generateGlobTasksSync('*.tmp'));
expectType<GlobTask[]>(generateGlobTasksSync(['a.tmp', '*.tmp', '!{c,d,e}.tmp']));

expectType<GlobTask[]>(generateGlobTasksSync('*.tmp', {expandDirectories: false}));
expectType<GlobTask[]>(
generateGlobTasksSync('*.tmp', {expandDirectories: ['a*', 'b*']}),
);
expectType<GlobTask[]>(
generateGlobTasksSync('*.tmp', {
expandDirectories: {
files: ['a', 'b'],
extensions: ['tmp'],
},
}),
);
expectType<GlobTask[]>(generateGlobTasksSync('*.tmp', {gitignore: true}));
expectType<GlobTask[]>(generateGlobTasksSync('*.tmp', {ignore: ['**/b.tmp']}));

// IsDynamicPattern
expectType<boolean>(isDynamicPattern('**'));
Expand Down
5 changes: 5 additions & 0 deletions package.json
Expand Up @@ -81,5 +81,10 @@
"ignores": [
"fixtures"
]
},
"ava": {
"files": [
"!tests/utilities.js"
]
}
}
8 changes: 7 additions & 1 deletion readme.md
Expand Up @@ -110,10 +110,16 @@ import {globbyStream} from 'globby';

### generateGlobTasks(patterns, options?)

Returns an `object[]` in the format `{pattern: string, options: Object}`, which can be passed as arguments to [`fast-glob`](https://github.com/mrmlnc/fast-glob). This is useful for other globbing-related packages.
Returns an `Promise<object[]>` in the format `{patterns: string[], options: Object}`, which can be passed as arguments to [`fast-glob`](https://github.com/mrmlnc/fast-glob). This is useful for other globbing-related packages.

Note that you should avoid running the same tasks multiple times as they contain a file system cache. Instead, run this method each time to ensure file system changes are taken into consideration.

### generateGlobTasksSync(patterns, options?)

Returns an `object[]` in the format `{patterns: string[], options: Object}`, which can be passed as arguments to [`fast-glob`](https://github.com/mrmlnc/fast-glob). This is useful for other globbing-related packages.

Takes the same arguments as `generateGlobTasks`.

### isDynamicPattern(patterns, options?)

Returns a `boolean` of whether there are any special glob characters in the `patterns`.
Expand Down
101 changes: 101 additions & 0 deletions tests/generate-glob-tasks.js
@@ -0,0 +1,101 @@
import util from 'node:util';
import process from 'node:process';
import path from 'node:path';
import test from 'ava';
import {
generateGlobTasks,
generateGlobTasksSync,
} from '../index.js';
import {
invalidPatterns,
getPathValues,
} from './utilities.js';

const runGenerateGlobTasks = async (t, patterns, options) => {
const promiseResult = await generateGlobTasks(patterns, options);
const syncResult = generateGlobTasksSync(patterns, options);

t.deepEqual(
promiseResult,
syncResult,
'generateGlobTasksSync() result is different than generateGlobTasks()',
);

return promiseResult;
};

test('generateGlobTasks', async t => {
const tasks = await runGenerateGlobTasks(t, ['*.tmp', '!b.tmp'], {ignore: ['c.tmp']});

t.is(tasks.length, 1);
t.deepEqual(tasks[0].patterns, ['*.tmp']);
t.deepEqual(tasks[0].options.ignore, ['c.tmp', 'b.tmp']);
await t.notThrowsAsync(generateGlobTasks('*'));
t.notThrows(() => generateGlobTasksSync('*'));
});

// Rejected for being an invalid pattern
for (const value of invalidPatterns) {
const valueString = util.format(value);
const message = 'Patterns must be a string or an array of strings';

test(`throws for invalid patterns input: ${valueString}`, async t => {
await t.throwsAsync(generateGlobTasks(value), {instanceOf: TypeError, message});
t.throws(() => generateGlobTasksSync(value), {instanceOf: TypeError, message});
});
}

test('throws when specifying a file as cwd', async t => {
const error = {message: 'The `cwd` option must be a path to a directory'};

for (const file of getPathValues(path.resolve('fixtures/gitignore/bar.js'))) {
// eslint-disable-next-line no-await-in-loop
await t.throwsAsync(generateGlobTasks('*', {cwd: file}), error);
t.throws(() => generateGlobTasksSync('*', {cwd: file}), error);
}
});

test('cwd', async t => {
const cwd = process.cwd();
for (const cwdDirectory of getPathValues(cwd)) {
// eslint-disable-next-line no-await-in-loop
const [task] = await runGenerateGlobTasks(t, ['*'], {cwd: cwdDirectory});
t.is(task.options.cwd, cwd);
}
});

test('expandDirectories option', async t => {
{
const tasks = await runGenerateGlobTasks(t, ['fixtures'], {ignore: ['fixtures/negative']});
t.is(tasks.length, 1);
t.deepEqual(tasks[0].patterns, ['fixtures/**']);
t.deepEqual(tasks[0].options.ignore, ['fixtures/negative/**']);
}

{
const tasks = await runGenerateGlobTasks(t, ['fixtures'], {ignore: ['fixtures/negative'], expandDirectories: false});
t.is(tasks.length, 1);
t.deepEqual(tasks[0].patterns, ['fixtures']);
t.deepEqual(tasks[0].options.ignore, ['fixtures/negative']);
}

{
const tasks = await runGenerateGlobTasks(t, ['fixtures'], {expandDirectories: ['a*', 'b*']});
t.is(tasks.length, 1);
t.deepEqual(tasks[0].patterns, ['fixtures/**/a*', 'fixtures/**/b*']);
t.deepEqual(tasks[0].options.ignore, []);
}

{
const tasks = await runGenerateGlobTasks(t, ['fixtures'], {
expandDirectories: {
files: ['a', 'b*'],
extensions: ['tmp', 'txt'],
},
ignore: ['**/b.tmp'],
});
t.is(tasks.length, 1);
t.deepEqual(tasks[0].patterns, ['fixtures/**/a.{tmp,txt}', 'fixtures/**/b*.{tmp,txt}']);
t.deepEqual(tasks[0].options.ignore, ['**/b.tmp']);
}
});

0 comments on commit 51c8f68

Please sign in to comment.