74 lines
1.9 KiB
JavaScript
74 lines
1.9 KiB
JavaScript
|
|
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 => ({
|
||
|
|
match: result[0],
|
||
|
|
index: result.index,
|
||
|
|
groups: result.slice(1),
|
||
|
|
namedGroups: result.groups ?? {},
|
||
|
|
input: result.input,
|
||
|
|
});
|
||
|
|
|
||
|
|
export function isMatch(regex, string, {timeout} = {}) {
|
||
|
|
try {
|
||
|
|
return functionTimeout(() => cloneRegexp(regex).test(string), {timeout})();
|
||
|
|
} catch (error) {
|
||
|
|
if (isTimeoutError(error)) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
throw error;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
export function firstMatch(regex, string, {timeout} = {}) {
|
||
|
|
try {
|
||
|
|
const result = functionTimeout(() => cloneRegexp(regex).exec(string), {timeout})();
|
||
|
|
|
||
|
|
if (result === null) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
return resultToMatch(result);
|
||
|
|
} catch (error) {
|
||
|
|
if (isTimeoutError(error)) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
throw error;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
export function matches(regex, string, {timeout = Number.POSITIVE_INFINITY, matchTimeout = Number.POSITIVE_INFINITY} = {}) {
|
||
|
|
if (!regex.global) {
|
||
|
|
throw new Error('The regex must have the global flag, otherwise, use `firstMatch()` instead');
|
||
|
|
}
|
||
|
|
|
||
|
|
return {
|
||
|
|
* [Symbol.iterator]() {
|
||
|
|
try {
|
||
|
|
const matches = string.matchAll(regex); // The regex is only executed when iterated over.
|
||
|
|
|
||
|
|
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;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
},
|
||
|
|
};
|
||
|
|
}
|