-
-
Notifications
You must be signed in to change notification settings - Fork 56
/
Copy pathinputs.ts
120 lines (107 loc) · 4.55 KB
/
inputs.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
import type { CharInput, Input } from './internal'
import type { EscapeChar } from './types/escape'
import type { Join } from './types/join'
import type { InputSource, MapToCapturedGroupsArr, MapToGroups, MapToValues } from './types/sources'
import type { IfUnwrapped } from './wrap'
import { createInput } from './internal'
import { wrap } from './wrap'
export type { Input }
const ESCAPE_REPLACE_RE = /[.*+?^${}()|[\]\\/]/g
function createCharInput<T extends string>(raw: T) {
const input = createInput(`[${raw}]`)
const from = <From extends string, To extends string>(charFrom: From, charTo: To) => createCharInput(`${raw}${escapeCharInput(charFrom)}-${escapeCharInput(charTo)}`)
const orChar = Object.assign(<T extends string>(chars: T) => createCharInput(`${raw}${escapeCharInput(chars)}`), { from })
return Object.assign(input, { orChar, from }) as CharInput<T>
}
function escapeCharInput<T extends string>(raw: T) {
return raw.replace(/[-\\^\]]/g, '\\$&') as EscapeChar<T>
}
/** This matches any character in the string provided */
export const charIn = Object.assign(<T extends string>(chars: T) => {
return createCharInput(escapeCharInput(chars))
}, createCharInput(''))
/** This matches any character that is not in the string provided */
export const charNotIn = Object.assign(<T extends string>(chars: T) => {
return createCharInput(`^${escapeCharInput(chars)}`)
}, createCharInput('^'))
/**
* This takes a variable number of inputs and matches any of them
* @example
* anyOf('foo', maybe('bar'), 'baz') // => /(?:foo|(?:bar)?|baz)/
* @argument inputs - arbitrary number of `string` or `Input`, where `string` will be escaped
*/
export function anyOf<Inputs extends InputSource[]>(...inputs: Inputs): Input<`(?:${Join<MapToValues<Inputs>>})`, MapToGroups<Inputs>, MapToCapturedGroupsArr<Inputs>> {
return createInput(`(?:${inputs.map(a => exactly(a)).join('|')})`)
}
export const char = createInput('.')
export const word = createInput('\\b\\w+\\b')
export const wordChar = createInput('\\w')
export const wordBoundary = createInput('\\b')
export const digit = createInput('\\d')
export const whitespace = createInput('\\s')
export const letter = Object.assign(createInput('[a-zA-Z]'), {
lowercase: createInput('[a-z]'),
uppercase: createInput('[A-Z]'),
})
export const tab = createInput('\\t')
export const linefeed = createInput('\\n')
export const carriageReturn = createInput('\\r')
export const not = {
word: createInput('\\W+'),
wordChar: createInput('\\W'),
wordBoundary: createInput('\\B'),
digit: createInput('\\D'),
whitespace: createInput('\\S'),
letter: Object.assign(createInput('[^a-zA-Z]'), {
lowercase: createInput('[^a-z]'),
uppercase: createInput('[^A-Z]'),
}),
tab: createInput('[^\\t]'),
linefeed: createInput('[^\\n]'),
carriageReturn: createInput('[^\\r]'),
}
/**
* Equivalent to `?` - takes a variable number of inputs and marks them as optional
* @example
* maybe('foo', exactly('ba?r')) // => /(?:fooba\?r)?/
* @argument inputs - arbitrary number of `string` or `Input`, where `string` will be escaped
*/
export function maybe<
Inputs extends InputSource[],
Value extends string = Join<MapToValues<Inputs>, '', ''>,
>(...inputs: Inputs): Input<
IfUnwrapped<Value, `(?:${Value})?`, `${Value}?`>,
MapToGroups<Inputs>,
MapToCapturedGroupsArr<Inputs>
> {
return createInput(`${wrap(exactly(...inputs))}?`)
}
/**
* This takes a variable number of inputs and concatenate their patterns, and escapes string inputs to match it exactly
* @example
* exactly('fo?o', maybe('bar')) // => /fo\?o(?:bar)?/
* @argument inputs - arbitrary number of `string` or `Input`, where `string` will be escaped
*/
export function exactly<Inputs extends InputSource[]>(...inputs: Inputs): Input<Join<MapToValues<Inputs>, '', ''>, MapToGroups<Inputs>, MapToCapturedGroupsArr<Inputs>> {
return createInput(
inputs
.map(input => (typeof input === 'string' ? input.replace(ESCAPE_REPLACE_RE, '\\$&') : input))
.join(''),
)
}
/**
* Equivalent to `+` - this takes a variable number of inputs and marks them as repeatable, any number of times but at least once
* @example
* oneOrMore('foo', maybe('bar')) // => /(?:foo(?:bar)?)+/
* @argument inputs - arbitrary number of `string` or `Input`, where `string` will be escaped
*/
export function oneOrMore<
Inputs extends InputSource[],
Value extends string = Join<MapToValues<Inputs>, '', ''>,
>(...inputs: Inputs): Input<
IfUnwrapped<Value, `(?:${Value})+`, `${Value}+`>,
MapToGroups<Inputs>,
MapToCapturedGroupsArr<Inputs>
> {
return createInput(`${wrap(exactly(...inputs))}+`)
}