Skip to content

Commit

Permalink
Added a createFilter() function, which works the same as `filePathF…
Browse files Browse the repository at this point in the history
…ilter()` but allows options to be set
  • Loading branch information
JamesMessinger committed Aug 19, 2019
1 parent aec1d0c commit 38b9c84
Show file tree
Hide file tree
Showing 5 changed files with 102 additions and 36 deletions.
53 changes: 53 additions & 0 deletions src/create-filter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { normalize } from "./normalize";
import { AnyFilter, FilterCriterion, FilterFunction, Filters, Options } from "./types";
import { _filters, PathFilter } from "./util";

/**
* Creates a `FilterFunction` that matches file paths based on the specified criteria.
*
* @param criteria - One or more glob patterns, regular expressions, or filter functions
* @returns A `FilterFunction` that matches file paths based on the specified criteria
*/
export function createFilter(options: Options, criteria: AnyFilter): FilterFunction;

/**
* Creates a `FilterFunction` that matches file paths based on the specified criteria.
*
* @param criteria - One or more glob patterns, regular expressions, or filter functions
* @returns A `FilterFunction` that matches file paths based on the specified criteria
*/
export function createFilter(options: Options, ...criteria: FilterCriterion[]): FilterFunction;

/**
* Creates a `FilterFunction` that matches file paths based on the specified criteria.
*
* @param filters - An object with `include` and `exclude` filter criteria
* @returns A `FilterFunction` that matches file paths based on the specified criteria
*/
export function createFilter(options: Options, filters: Filters): FilterFunction;

export function createFilter(options: Options, ...args: unknown[]): FilterFunction {
let criteria = args.length <= 1 ? args[0] as AnyFilter : args as FilterCriterion[];
let filters = normalize(criteria, options);

(pathFilter as PathFilter)[_filters] = filters;
return pathFilter;

// tslint:disable-next-line: no-shadowed-variable
function pathFilter(...args: unknown[]): boolean {
// Does the file path match any of the exclude filters?
let exclude = filters.exclude.some((filter) => filter(...args));
if (exclude) {
return false;
}

if (filters.include.length === 0) {
// Include everything that's not excluded
return true;
}

// Does the file path match any of the include filters?
let include = filters.include.some((filter) => filter(...args));
return include;
}
}
21 changes: 1 addition & 20 deletions src/file-path-filter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,24 +26,5 @@ export function filePathFilter(...criteria: FilterCriterion[]): FilterFunction;
export function filePathFilter(filters: Filters): FilterFunction;

export function filePathFilter(...args: unknown[]): FilterFunction {
let filters = args.length <= 1 ? normalize(args[0] as AnyFilter) : normalize(args as FilterCriterion[]);
(pathFilter as PathFilter)[_filters] = filters;
return pathFilter;

function pathFilter(filePath: string, ...other: unknown[]): boolean {
// Does the file path match any of the exclude filters?
let exclude = filters.exclude.some((filter) => filter(filePath, ...other));
if (exclude) {
return false;
}

if (filters.include.length === 0) {
// Include everything that's not excluded
return true;
}

// Does the file path match any of the include filters?
let include = filters.include.some((filter) => filter(filePath, ...other));
return include;
}
return createFilter({}, ...args as FilterCriterion[]);
}
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { filePathFilter } from "./file-path-filter";

export * from "./types";
export { createFilter } from "./create-filter";
export { filePathFilter };

// Export `filePathFilter` as a named export and the default export
Expand Down
49 changes: 36 additions & 13 deletions src/normalize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,16 @@ const isWindows = process.platform === "win32";
* Normalizes the user-provided filter criteria. The normalized form is a `Filters` object
* whose `include` and `exclude` properties are both `FilterFunction` arrays.
*/
export function normalize(criteria: AnyFilter): Filters<FilterFunction[]> {
export function normalize(criteria: AnyFilter, opts: Options): Filters<FilterFunction[]> {
let filters: Filters<FilterFunction[]> = {
include: [],
exclude: [],
};

let options = normalizeOptions(opts);

// Convert each criterion to a FilterFunction
let tuples = normalizeCriteria(criteria);
let tuples = normalizeCriteria(criteria, options);

// Populate the `include` and `exclude` arrays
for (let [filter, filterFunction] of tuples) {
Expand All @@ -25,15 +27,27 @@ export function normalize(criteria: AnyFilter): Filters<FilterFunction[]> {
return filters;
}

type NormalizedOptions = Required<Options>;

/**
* Fills-in defaults for any options that weren't specified by the caller.
*/
function normalizeOptions(options: Options): NormalizedOptions {
return {
getPath: options.getPath || String,
};
}

/**
* Creates a `FilterFunction` for each given criterion.
*/
function normalizeCriteria(criteria: AnyFilter, filter?: Filter): Array<[Filter, FilterFunction]> {
function normalizeCriteria(
criteria: AnyFilter, options: NormalizedOptions, filter?: Filter): Array<[Filter, FilterFunction]> {
let tuples: Array<[Filter, FilterFunction]> = [];

if (Array.isArray(criteria)) {
for (let criterion of criteria) {
tuples.push(...normalizeCriteria(criterion, filter));
tuples.push(...normalizeCriteria(criterion, options, filter));
}
}
else if (isPathFilter(criteria)) {
Expand All @@ -45,14 +59,14 @@ function normalizeCriteria(criteria: AnyFilter, filter?: Filter): Array<[Filter,
}
}
else if (isFilterCriterion(criteria)) {
tuples.push(normalizeCriterion(criteria, filter));
tuples.push(normalizeCriterion(criteria, options, filter));
}
else if (criteria && typeof criteria === "object" && !filter) {
if (criteria.include !== undefined) {
tuples.push(...normalizeCriteria(criteria.include, "include"));
tuples.push(...normalizeCriteria(criteria.include, options, "include"));
}
if (criteria.exclude !== undefined) {
tuples.push(...normalizeCriteria(criteria.exclude, "exclude"));
tuples.push(...normalizeCriteria(criteria.exclude, options, "exclude"));
}
}
else {
Expand All @@ -63,12 +77,14 @@ function normalizeCriteria(criteria: AnyFilter, filter?: Filter): Array<[Filter,
}

/**
* Creates a `FilterFunction` for each given criterion.
* Creates a `FilterFunction` for the given criterion.
*
* @param criteria - One or more filter critiera
* @param options - Options for how the `FilterFunction` should behave
* @param filter - The type of filter. Defaults to `include`, except for glob patterns that start with "!"
*/
function normalizeCriterion(criterion: FilterCriterion, filter?: Filter): [Filter, FilterFunction] {
function normalizeCriterion(
criterion: FilterCriterion, options: NormalizedOptions, filter?: Filter): [Filter, FilterFunction] {
const globOptions = { extended: true, globstar: true };
let type = typeof criterion;
let filterFunction: FilterFunction;
Expand All @@ -93,11 +109,14 @@ function normalizeCriterion(criterion: FilterCriterion, filter?: Filter): [Filte
}

let pattern = GlobToRegExp(glob, globOptions);
filterFunction = createGlobFilter(pattern, invert);
filterFunction = createGlobFilter(pattern, options, invert);
}
else if (criterion instanceof RegExp) {
let pattern = criterion;
filterFunction = function regExpFilter(filePath: string) {
let { getPath } = options;

filterFunction = function regExpFilter(...args: unknown[]) {
let filePath = getPath(...args);
return pattern.test(filePath);
};
}
Expand All @@ -111,8 +130,12 @@ function normalizeCriterion(criterion: FilterCriterion, filter?: Filter): [Filte
/**
* Creates a `FilterFunction` for filtering based on glob patterns
*/
function createGlobFilter(pattern: RegExp, invert: boolean): FilterFunction {
return function globFilter(filePath: string) {
function createGlobFilter(pattern: RegExp, options: NormalizedOptions, invert: boolean): FilterFunction {
let { getPath } = options;

return function globFilter(...args: unknown[]) {
let filePath = getPath(...args);

if (isWindows) {
// Glob patterns only use forward slashes, even on Windows
filePath = filePath.replace(/\\/g, "/");
Expand Down
14 changes: 11 additions & 3 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
// tslint:disable: no-any

/**
* A function that filters files.
*/
export type FilterFunction = (filePath: string, ...args: unknown[]) => unknown;
export type FilterFunction = (...args: unknown[]) => unknown;

/**
* A single filter criterion.
Expand Down Expand Up @@ -34,8 +32,18 @@ export interface Filters<T = FilterCriteria> {
export type AnyFilter = FilterCriteria | Partial<Filters>;

/**
* Options for creating a custom file filter
*/
export interface Options {
/**
* A function taht returns the file path from the given arguments.
*
* Defaults to a function that returns the first argument as a string.
*/
getPath?: PathGetter;
}

/**
* A function that returns the file path from the given arguments
*/
export type PathGetter = (...args: unknown[]) => string;

0 comments on commit 38b9c84

Please sign in to comment.