Skip to content

Commit

Permalink
Make matches() return an iterator (#3)
Browse files Browse the repository at this point in the history
  • Loading branch information
Richienb committed Jun 27, 2022
1 parent 6902355 commit d3a07ad
Show file tree
Hide file tree
Showing 6 changed files with 53 additions and 20 deletions.
13 changes: 10 additions & 3 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@ export type Match = {
input: string;
};

export interface MatchesOptions extends Options {
/**
The time in milliseconds to wait before timing out when searching for each match.
*/
readonly matchTimeout?: number;
}

/**
Returns a boolean for whether the given `regex` matches the given `string`.
Expand Down Expand Up @@ -39,7 +46,7 @@ console.log(firstMatch(/\d+/, getUserInput(), {timeout: 1000}));
export function firstMatch(regex: RegExp, string: string, options?: Options): Match | undefined;

/**
Returns an array of matches.
Returns an iterable of matches.
If the regex takes longer to match than the given timeout, it returns an empty array.
Expand All @@ -49,9 +56,9 @@ __The `regex` must have the `/g` flag.__
```
import {matches} from 'super-regex';
console.log(matches(/\d+/, getUserInput(), {timeout: 1000}));
console.log([...matches(/\d+/, getUserInput(), {timeout: 1000})]);
```
*/
export function matches(regex: RegExp, string: string, options?: Options): Match[];
export function matches(regex: RegExp, string: string, options?: MatchesOptions): Iterable<Match>;

export {Options} from 'function-timeout';
36 changes: 26 additions & 10 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import functionTimeout, {isTimeoutError} from 'function-timeout';
import timeSpan from 'time-span';
import cloneRegexp from 'clone-regexp'; // TODO: Use `structuredClone` instead when targeting Node.js 18.

const resultToMatch = result => ({
Expand Down Expand Up @@ -39,15 +40,30 @@ export function firstMatch(regex, string, {timeout} = {}) {
}
}

export function matches(regex, string, {timeout} = {}) {
try {
return functionTimeout(() => [...string.matchAll(regex)], {timeout})()
.map(result => resultToMatch(result));
} catch (error) {
if (isTimeoutError(error)) {
return [];
}
export function matches(regex, string, {timeout = Number.POSITIVE_INFINITY, matchTimeout = Number.POSITIVE_INFINITY} = {}) {
return {
* [Symbol.iterator]() {
try {
const matches = string.matchAll(regex); // The regex is only executed when iterated over.

throw error;
}
while (true) {
const nextMatch = functionTimeout(() => matches.next(), {timeout: (timeout !== Number.POSITIVE_INFINITY || matchTimeout !== Number.POSITIVE_INFINITY) ? Math.min(timeout, matchTimeout) : undefined}); // `matches.next` must be called within an arrow function so that it doesn't loose its context.

const end = timeSpan();
const {value, done} = nextMatch();
timeout -= Math.ceil(end());

if (done) {
break;
}

yield resultToMatch(value);
}
} catch (error) {
if (!isTimeoutError(error)) {
throw error;
}
}
},
};
}
2 changes: 1 addition & 1 deletion index.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ import {isMatch, firstMatch, matches, Match} from './index.js';

expectType<boolean>(isMatch(/\d/, '1', {timeout: 1000}));
expectType<Match | undefined>(firstMatch(/\d/, '1', {timeout: 1000}));
expectType<Match[]>(matches(/\d/, '1', {timeout: 1000}));
expectType<Iterable<Match>>(matches(/\d/, '1', {timeout: 1000, matchTimeout: 1000}));
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@
],
"dependencies": {
"clone-regexp": "^3.0.0",
"function-timeout": "^0.1.0"
"function-timeout": "^0.1.0",
"time-span": "^5.1.0"
},
"devDependencies": {
"ava": "^4.3.0",
Expand Down
10 changes: 9 additions & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ If the regex takes longer to match than the given timeout, it returns `undefined

### matches(regex, string, options?)

Returns an array of `Match`'es.
Returns an iterable of `Match`'es.

If the regex takes longer to match than the given timeout, it returns an empty array.

Expand All @@ -56,6 +56,14 @@ Type: `number` *(integer)*

The time in milliseconds to wait before timing out.

##### matchTimeout?

Type: `number` *(integer)*

Only works in `matches()`.

The time in milliseconds to wait before timing out when searching for each match.

### Match

```ts
Expand Down
9 changes: 5 additions & 4 deletions test.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@ test('firstMatch', t => {
});

test('matches', t => {
t.deepEqual(matches(fixtureRegex(), fixtureString, {timeout: 10}), []);
t.is(matches(fixtureRegex(), 'v1.1.3', {timeout: 10})[0].match, '1.1.3');
t.is(matches(/^v\d+/g, fixtureString, {timeout: 1000})[0].match, 'v1');
t.is(matches(/^v\d+/g, fixtureString)[0].match, 'v1');
t.deepEqual([...matches(fixtureRegex(), fixtureString, {timeout: 10})], []);
t.is([...matches(fixtureRegex(), 'v1.1.3', {timeout: 10})][0].match, '1.1.3');
t.is([...matches(/^v\d+/g, fixtureString, {timeout: 1000})][0].match, 'v1');
t.is([...matches(/^v\d+/g, fixtureString, {timeout: 1000, matchTimeout: 1000})][0].match, 'v1');
t.is([...matches(/^v\d+/g, fixtureString)][0].match, 'v1');
});

0 comments on commit d3a07ad

Please sign in to comment.