Skip to content

Commit

Permalink
Implement ofName filter
Browse files Browse the repository at this point in the history
The `ofName` filter has been implemented in order to find files by base names without extension name.
A `ConflictError` interface has been added to add the conflicting elements of `findOnlyFile` calls.
Tests have been added to the exported members of `iterable.ts` which had been covered by the tests for file finders.
  • Loading branch information
MartyO256 committed Jan 8, 2019
1 parent c95556b commit 6d69b52
Show file tree
Hide file tree
Showing 10 changed files with 266 additions and 90 deletions.
20 changes: 13 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# find-files-by-patterns

[![npm Information Page](https://img.shields.io/npm/v/find-files-by-patterns.svg)](https://www.npmjs.com/package/find-files-by-patterns)
![Node Version](https://img.shields.io/badge/node-%3E%3D%2010.0.0-green.svg)
[![Documentation](https://img.shields.io/website-up-down-green-red/https/shields.io.svg?label=documentation)](https://martyo256.github.io/find-files-by-patterns/)
[![Documentation](https://img.shields.io/website-up-down-green-red/https/martyo256.github.io/find-files-by-patterns.svg?label=documentation)](https://martyo256.github.io/find-files-by-patterns/)
[![Build Status](https://travis-ci.org/MartyO256/find-files-by-patterns.svg)](https://travis-ci.org/MartyO256/find-files-by-patterns)
[![Coverage Status](https://coveralls.io/repos/github/MartyO256/find-files-by-patterns/badge.svg)](https://coveralls.io/github/MartyO256/find-files-by-patterns?branch=development)
[![Maintainability](https://api.codeclimate.com/v1/badges/6d2069677a848c509e3a/maintainability)](https://codeclimate.com/github/MartyO256/find-files-by-patterns/maintainability)
Expand Down Expand Up @@ -69,7 +70,7 @@ const { findFile, upwardDirectories,
* [`upwardDirectories` and `upwardDirectoriesSync`](#upwarddirectories-and-upwarddirectoriessync)
* [`downwardFiles` and `downwardFilesSync`](#downwardfiles-and-downwardfilessync)
* [`upwardFiles` and `upwardFilesSync`](#upwardfiles-and-upwardfilessync)
* [`ofBasename`, `ofDirname` and `ofExtname`](#ofbasename-ofdirname-and-ofextname)
* [`ofBasename`, `ofName`, `ofDirname` and `ofExtname`](#ofbasename-ofname-ofdirname-and-ofextname)
* [`hasPathSegments`](#haspathsegments)
* [`isFile` and `isFileSync`](#isfile-and-isfilesync)
* [`isDirectory` and `isDirectorySync`](#isdirectory-and-isdirectorysync)
Expand Down Expand Up @@ -117,7 +118,7 @@ Directories:

Filters:

- [`ofBasename`, `ofDirname` and `ofExtname`](#ofBasename-ofDirname-and-ofExtname)
- [`ofBasename`, `ofName`, `ofDirname` and `ofExtname`](#ofBasename-ofName-ofDirname-and-ofExtname)
- [`hasPathSegments`](#hasPathSegments)
- [`isFile` and `isFileSync`](#isFile-and-isFileSync)
- [`isDirectory` and `isDirectorySync`](#isDirectory-and-isDirectorySync)
Expand Down Expand Up @@ -353,10 +354,10 @@ upwardFilesSync(startPath: string, maximumHeight: number): Iterable<string>;
upwardFilesSync(startPath: string, endPath: string): Iterable<string>;
```

### `ofBasename`, `ofDirname` and `ofExtname`
### `ofBasename`, `ofName`, `ofDirname` and `ofExtname`

Determines whether or not a path's `basename`, `dirname` or `extname` matches
any of a sequence of segment testers.
Determines whether or not a path's `basename`, `name`, `dirname` or `extname`
matches any of a sequence of segment testers.
A segment tester can be a string, a regular expression or a function.

- If it is a string, then the segment must correspond to it for the path to
Expand All @@ -371,21 +372,26 @@ A segment tester can be a string, a regular expression or a function.
```ts
type SegmentTester = string | RegExp | ((segment: string) => boolean);
ofBasename(...tests: SegmentTester[]): FilterSync<string>;
ofName(...tests: SegmentTester[]): FilterSync<string>;
ofDirname(...tests: SegmentTester[]): FilterSync<string>;
ofExtname(...tests: SegmentTester[]): FilterSync<string>;
```

> Example
```js
const { ofBasename, ofDirname, ofExtname } = require("find-files-by-patterns");
const { ofBasename, ofName,
ofDirname, ofExtname } = require("find-files-by-patterns");

const isMarkdownFile = ofExtname(".md", ".markdown");
const isIndexFile = ofName("index");
const isDataFilename = ofBasename(/^data/);
const isInDocuments = ofDirname("/Documents");

isMarkdownFile("/Documents/article.md"); //=> `true`
isMarkdownFile("/Documents/data.json"); //=> `false`
isIndexFile("/Documents/index.html"); //=> `true`
isIndexFile("/Documents/index.md"); //=> `true`
isDataFilename("/Documents/data.json"); //=> `true`
isInDocuments("/Documents/data.json"); //=> `true`
isInDocuments("/Documents/src/index.js"); //=> `false`
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "find-files-by-patterns",
"version": "1.0.1",
"version": "1.1.0",
"description": "Find files by patterns in directories, upwards or downwards from other paths.",
"license": "MIT",
"author": "Marc-Antoine Ouimet <ouimetmarcantoine@gmail.com>",
Expand Down
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@ export {
firstElementSync,
onlyElement,
onlyElementSync,
ConflictError,
} from "./iterable";
export { readdir, readdirSync, readdirs, readdirsSync } from "./readdirs";

export {
hasPathSegments,
ofBasename,
ofDirname,
ofName,
ofExtname,
segments,
} from "./path";
Expand Down
55 changes: 28 additions & 27 deletions src/iterable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,30 +18,13 @@ export const isIterable = <T>(object): object is Iterable<T> =>
export const isAsyncIterable = <T>(object): object is AsyncIterable<T> =>
typeof object[Symbol.asyncIterator] === "function";

/**
* Converts an asynchronous iterable to an array. The given iterable should be
* finite.
* @param iterable The finite iterable to convert.
* @returns The array consisting of the elements asynchronously iterated over by
* the given iterable.
*/
export const asyncIterableToArray = async <T>(
iterable: AsyncIterable<T>,
): Promise<T[]> => {
const array: T[] = [];
for await (const element of iterable) {
array.push(element);
}
return array;
};

/**
* Retrieves the first element of an iterable.
* @param iterable The iterable from which to retrieve the first element.
* @return The first element of the given iterable.
*/
export const firstElement = async <T>(
iterable: AsyncIterable<T>,
iterable: Iterable<T> | AsyncIterable<T>,
): Promise<T | null> => {
for await (const element of iterable) {
return element;
Expand All @@ -61,14 +44,26 @@ export const firstElementSync = <T>(iterable: Iterable<T>): T | null => {
return null;
};

/**
* A conflict error occurs when multiple elements should not be yielded by an
* iterable.
*/
export interface ConflictError<T> extends Error {
/**
* The conflicting elements which are the cause of the error.
*/
conflicts: T[];
}

/**
* Constructs an error for conflicting elements.
* @param firstElement The first conflicting element.
* @param secondElement The second conflicting element.
* @param conflicts The conflicting elements.
* @returns The error to throw in case of conflicting elements.
*/
const conflictingElementsError = <T>(firstElement: T, secondElement: T) =>
new Error(`${firstElement} is conflicting with ${secondElement}`);
const conflictingElementsError = <T>(...conflicts: T[]): ConflictError<T> => ({
...new Error(`The following elements are conflicting: ${conflicts}`),
conflicts,
});

/**
* Retrieves the first and only element of an iterable.
Expand All @@ -78,14 +73,14 @@ const conflictingElementsError = <T>(firstElement: T, secondElement: T) =>
* @return The first and only element of the given iterable.
*/
export const onlyElement = async <T>(
iterable: AsyncIterable<T>,
iterable: Iterable<T> | AsyncIterable<T>,
): Promise<T | null> => {
let retainedElement: T;
for await (const element of iterable) {
if (retainedElement === undefined) {
retainedElement = element;
} else {
throw conflictingElementsError(element, retainedElement);
throw conflictingElementsError(retainedElement, element);
}
}
return retainedElement;
Expand All @@ -104,7 +99,7 @@ export const onlyElementSync = <T>(iterable: Iterable<T>): T | null => {
if (retainedElement === undefined) {
retainedElement = element;
} else {
throw conflictingElementsError(element, retainedElement);
throw conflictingElementsError(retainedElement, element);
}
}
return retainedElement;
Expand All @@ -117,8 +112,14 @@ export const onlyElementSync = <T>(iterable: Iterable<T>): T | null => {
* order.
*/
export const allElements = async <T>(
iterable: AsyncIterable<T>,
): Promise<T[]> => asyncIterableToArray(iterable);
iterable: Iterable<T> | AsyncIterable<T>,
): Promise<T[]> => {
const array: T[] = [];
for await (const element of iterable) {
array.push(element);
}
return array;
};

/**
* Retrieves all the elements of an iterable. The iterable should be finite.
Expand Down
21 changes: 18 additions & 3 deletions src/path.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,22 +65,37 @@ const ofSegment = (
* a path's base name is equal to any of the given string base names, or if it
* matches with any of the regular expressions, or if any of the base name
* functions returns `true`, then the filter returns `true`.
* @param basenames The sequence of base names on which to test paths.
* @param tests The set of tests run on the paths to check.
* @throws If a test function throws an error as it is being run.
* @returns A filter which determines whether or not a given path has a base
* name matching a given full base name, a regular expression or a function.
*/
export const ofBasename = (...tests: SegmentTester[]): FilterSync<string> =>
ofSegment(tests, basename);

/**
* Constructs a filter which determines whether or not a given path has a name
* matching a given full name, a regular expression or a function. The name of a
* path corresponds to its base name without its extension name. If a path's
* name is equal to any of the given string names, or if it matches with any of
* the regular expressions, or if any of the name functions returns `true`, then
* the filter returns `true`.
* @param tests The set of tests run on the paths to check.
* @throws If a test function throws an error as it is being run.
* @returns A filter which determines whether or not a given path has a base
* name matching a given full base name, a regular expression or a function.
*/
export const ofName = (...tests: SegmentTester[]): FilterSync<string> =>
ofSegment(tests, (path: string) => basename(path, extname(path)));

/**
* Constructs a filter which determines whether or not a given path has a
* directory name matching a given full directory name, a regular expression or
* a function. If a path's directory name is equal to any of the given string
* directory names, or if it matches with any of the regular expressions, or if
* any of the directory name functions returns `true`, then the filter returns
* `true`.
* @param dirnames The sequence of directory names on which to test paths.
* @param tests The set of tests run on the paths to check.
* @throws If any of the test functions throws an error.
* @returns A filter which determines whether or not a given path has a
* directory name matching a given full directory name, a regular expression or
Expand All @@ -96,7 +111,7 @@ export const ofDirname = (...tests: SegmentTester[]): FilterSync<string> =>
* extension names, or if it matches with any of the regular expressions, or if
* any of the extension name functions returns `true`, then the filter returns
* `true`.
* @param extnames The sequence of extension names on which to test paths.
* @param tests The set of tests run on the paths to check.
* @throws If any of the test functions throws an error.
* @returns A filter which determines whether or not a given path has an
* extension name matching a given full extension name, a regular expression or
Expand Down
10 changes: 5 additions & 5 deletions test/filter.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
FilterSync,
filterSync,
} from "../src/filter";
import { asyncIterableToArray } from "../src/iterable";
import { allElements } from "../src/iterable";

describe("filter", () => {
const isEvenSync: FilterSync<number> = (element: number) => element % 2 === 0;
Expand Down Expand Up @@ -99,14 +99,14 @@ describe("filter", () => {
const filteredElements = [4, 6];
it("should filter in the correct amount of elements", async () => {
assert.strictEqual(
(await asyncIterableToArray(
(await allElements(
filter(elements, conjunction([isEven, isGreaterThan2])),
)).length,
filteredElements.length,
);
});
it("should only filter in elements that pass all of the filters", async () => {
for (const element of await asyncIterableToArray(
for (const element of await allElements(
filter(elements, conjunction([isEven, isGreaterThan2])),
)) {
assert.isTrue(
Expand All @@ -117,14 +117,14 @@ describe("filter", () => {
});
it("should filter in all the elements that pass all of the filters", async () => {
assert.deepStrictEqual(
await asyncIterableToArray(
await allElements(
filter(elements, conjunction([isEven, isGreaterThan2])),
),
filteredElements,
);
});
it("should throw an error if any of the filters throws an error", async () => {
rejects(asyncIterableToArray(filter(elements, error)));
rejects(allElements(filter(elements, error)));
});
});
describe("filterSync", () => {
Expand Down
Loading

0 comments on commit 6d69b52

Please sign in to comment.