Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expose new generateGlobTasks and generateGlobTasksSync #221

Merged
merged 6 commits into from Jan 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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']);
}
});