|
1 | 1 | import { exactly } from './inputs'
|
2 | 2 | import type { GetValue } from './types/escape'
|
3 |
| -import type { InputSource } from './types/sources' |
| 3 | +import type { GetCapturedGroupsArr, InputSource } from './types/sources' |
4 | 4 | import { IfUnwrapped, wrap } from './wrap'
|
5 | 5 |
|
6 | 6 | const GROUPED_AS_REPLACE_RE = /^(?:\(\?:(.+)\)|(\(?.+\)?))$/
|
7 |
| -const GROUPED_REPLACE_RE = /^(?:\(\??:?(.+)\)([?+*]|{[\d,]+})?|(.+))$/ |
| 7 | +const GROUPED_REPLACE_RE = /^(?:\(\?:(.+)\)([?+*]|{[\d,]+})?|(.+))$/ |
8 | 8 |
|
9 |
| -export interface Input<V extends string, G extends string = never> { |
| 9 | +export interface Input< |
| 10 | + V extends string, |
| 11 | + G extends string = never, |
| 12 | + C extends (string | undefined)[] = [] |
| 13 | +> { |
10 | 14 | and: {
|
11 | 15 | /** this adds a new pattern to the current input */
|
12 | 16 | <I extends InputSource<string, any>>(input: I): Input<
|
13 | 17 | `${V}${GetValue<I>}`,
|
14 |
| - G | (I extends Input<any, infer NewGroups> ? NewGroups : never) |
| 18 | + G | (I extends Input<any, infer NewGroups> ? NewGroups : never), |
| 19 | + [...C, ...GetCapturedGroupsArr<I>] |
15 | 20 | >
|
16 | 21 | /** this adds a new pattern to the current input, with the pattern reference to a named group. */
|
17 |
| - referenceTo: <N extends G>(groupName: N) => Input<`${V}\\k<${N}>`, G> |
| 22 | + referenceTo: <N extends G>(groupName: N) => Input<`${V}\\k<${N}>`, G, C> |
18 | 23 | }
|
19 | 24 | /** this provides an alternative to the current input */
|
20 | 25 | or: <I extends InputSource<string, any>>(
|
21 | 26 | input: I
|
22 | 27 | ) => Input<
|
23 | 28 | `(?:${V}|${GetValue<I>})`,
|
24 |
| - G | (I extends Input<any, infer NewGroups> ? NewGroups : never) |
| 29 | + G | (I extends Input<any, infer NewGroups> ? NewGroups : never), |
| 30 | + [...C, ...GetCapturedGroupsArr<I>] |
25 | 31 | >
|
26 | 32 | /** this is a positive lookbehind. Make sure to check [browser support](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp#browser_compatibility) as not all browsers support lookbehinds (notably Safari) */
|
27 |
| - after: <I extends InputSource<string>>(input: I) => Input<`(?<=${GetValue<I>})${V}`, G> |
| 33 | + after: <I extends InputSource<string>>( |
| 34 | + input: I |
| 35 | + ) => Input<`(?<=${GetValue<I>})${V}`, G, [...GetCapturedGroupsArr<I>, ...C]> |
28 | 36 | /** this is a positive lookahead */
|
29 |
| - before: <I extends InputSource<string>>(input: I) => Input<`${V}(?=${GetValue<I>})`, G> |
| 37 | + before: <I extends InputSource<string>>( |
| 38 | + input: I |
| 39 | + ) => Input<`${V}(?=${GetValue<I>})`, G, [...C, ...GetCapturedGroupsArr<I>]> |
30 | 40 | /** these is a negative lookbehind. Make sure to check [browser support](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp#browser_compatibility) as not all browsers support lookbehinds (notably Safari) */
|
31 |
| - notAfter: <I extends InputSource<string>>(input: I) => Input<`(?<!${GetValue<I>})${V}`, G> |
| 41 | + notAfter: <I extends InputSource<string>>( |
| 42 | + input: I |
| 43 | + ) => Input<`(?<!${GetValue<I>})${V}`, G, [...GetCapturedGroupsArr<I, true>, ...C]> |
32 | 44 | /** this is a negative lookahead */
|
33 |
| - notBefore: <I extends InputSource<string>>(input: I) => Input<`${V}(?!${GetValue<I>})`, G> |
| 45 | + notBefore: <I extends InputSource<string>>( |
| 46 | + input: I |
| 47 | + ) => Input<`${V}(?!${GetValue<I>})`, G, [...C, ...GetCapturedGroupsArr<I, true>]> |
34 | 48 | times: {
|
35 | 49 | /** repeat the previous pattern an exact number of times */
|
36 | 50 | <N extends number>(number: N): IfUnwrapped<
|
37 | 51 | V,
|
38 |
| - Input<`(?:${V}){${N}}`, G>, |
39 |
| - Input<`${V}{${N}}`, G> |
| 52 | + Input<`(?:${V}){${N}}`, G, C>, |
| 53 | + Input<`${V}{${N}}`, G, C> |
40 | 54 | >
|
41 | 55 | /** specify that the expression can repeat any number of times, _including none_ */
|
42 |
| - any: () => IfUnwrapped<V, Input<`(?:${V})*`, G>, Input<`${V}*`, G>> |
| 56 | + any: () => IfUnwrapped<V, Input<`(?:${V})*`, G>, Input<`${V}*`, G, C>> |
43 | 57 | /** specify that the expression must occur at least x times */
|
44 | 58 | atLeast: <N extends number>(
|
45 | 59 | number: N
|
46 |
| - ) => IfUnwrapped<V, Input<`(?:${V}){${N},}`, G>, Input<`${V}{${N},}`, G>> |
| 60 | + ) => IfUnwrapped<V, Input<`(?:${V}){${N},}`, G>, Input<`${V}{${N},}`, G, C>> |
47 | 61 | /** specify a range of times to repeat the previous pattern */
|
48 | 62 | between: <Min extends number, Max extends number>(
|
49 | 63 | min: Min,
|
50 | 64 | max: Max
|
51 |
| - ) => IfUnwrapped<V, Input<`(?:${V}){${Min},${Max}}`, G>, Input<`${V}{${Min},${Max}}`, G>> |
| 65 | + ) => IfUnwrapped<V, Input<`(?:${V}){${Min},${Max}}`, G, C>, Input<`${V}{${Min},${Max}}`, G, C>> |
52 | 66 | }
|
53 | 67 | /** this defines the entire input so far as a named capture group. You will get type safety when using the resulting RegExp with `String.match()`. Alias for `groupedAs` */
|
54 | 68 | as: <K extends string>(
|
55 | 69 | key: K
|
56 |
| - ) => Input<`(?<${K}>${V extends `(?:${infer S extends string})` ? S : V})`, G | K> |
| 70 | + ) => Input< |
| 71 | + `(?<${K}>${V extends `(?:${infer S})` ? S : V})`, |
| 72 | + G | K, |
| 73 | + [`(?<${K}>${V extends `(?:${infer S})` ? S : V})`, ...C] |
| 74 | + > |
57 | 75 | /** this defines the entire input so far as a named capture group. You will get type safety when using the resulting RegExp with `String.match()` */
|
58 | 76 | groupedAs: <K extends string>(
|
59 | 77 | key: K
|
60 |
| - ) => Input<`(?<${K}>${V extends `(?:${infer S extends string})` ? S : V})`, G | K> |
| 78 | + ) => Input< |
| 79 | + `(?<${K}>${V extends `(?:${infer S})` ? S : V})`, |
| 80 | + G | K, |
| 81 | + [`(?<${K}>${V extends `(?:${infer S})` ? S : V})`, ...C] |
| 82 | + > |
61 | 83 | /** this capture the entire input so far as an anonymous group */
|
62 |
| - grouped: () => Input<V extends `(?:${infer S})${infer E}` ? `(${S})${E}` : `(${V})`, G> |
| 84 | + grouped: () => Input< |
| 85 | + V extends `(?:${infer S})${infer E}` ? `(${S})${E}` : `(${V})`, |
| 86 | + G, |
| 87 | + [V extends `(?:${infer S})${'' | '?' | '+' | '*' | `{${string}}`}` ? `(${S})` : `(${V})`, ...C] |
| 88 | + > |
63 | 89 | /** this allows you to match beginning/ends of lines with `at.lineStart()` and `at.lineEnd()` */
|
64 | 90 | at: {
|
65 |
| - lineStart: () => Input<`^${V}`, G> |
66 |
| - lineEnd: () => Input<`${V}$`, G> |
| 91 | + lineStart: () => Input<`^${V}`, G, C> |
| 92 | + lineEnd: () => Input<`${V}$`, G, C> |
67 | 93 | }
|
68 | 94 | /** this allows you to mark the input so far as optional */
|
69 |
| - optionally: () => IfUnwrapped<V, Input<`(?:${V})?`, G>, Input<`${V}?`, G>> |
| 95 | + optionally: () => IfUnwrapped<V, Input<`(?:${V})?`, G, C>, Input<`${V}?`, G, C>> |
70 | 96 | toString: () => string
|
71 | 97 | }
|
72 | 98 |
|
73 |
| -export const createInput = <Value extends string, Groups extends string = never>( |
74 |
| - s: Value | Input<Value, Groups> |
75 |
| -): Input<Value, Groups> => { |
| 99 | +export const createInput = < |
| 100 | + Value extends string, |
| 101 | + Groups extends string = never, |
| 102 | + CaptureGroupsArr extends (string | undefined)[] = [] |
| 103 | +>( |
| 104 | + s: Value | Input<Value, Groups, CaptureGroupsArr> |
| 105 | +): Input<Value, Groups, CaptureGroupsArr> => { |
76 | 106 | const groupedAsFn = (key: string) =>
|
77 | 107 | createInput(`(?<${key}>${`${s}`.replace(GROUPED_AS_REPLACE_RE, '$1$2')})`)
|
78 | 108 |
|
|
0 commit comments