Skip to content

Commit a386d00

Browse files
authored
feat: add orChar/from chain for charIn/charNotIn (#399)
1 parent 4be3c53 commit a386d00

File tree

3 files changed

+54
-7
lines changed

3 files changed

+54
-7
lines changed

src/core/inputs.ts

+18-7
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { Input } from './internal'
1+
import type { CharInput, Input } from './internal'
22
import type { EscapeChar } from './types/escape'
33
import type { Join } from './types/join'
44
import type { InputSource, MapToCapturedGroupsArr, MapToGroups, MapToValues } from './types/sources'
@@ -11,16 +11,27 @@ export type { Input }
1111

1212
const ESCAPE_REPLACE_RE = /[.*+?^${}()|[\]\\/]/g
1313

14-
/** This matches any character in the string provided */
15-
export function charIn<T extends string>(chars: T) {
16-
return createInput(`[${chars.replace(/[-\\^\]]/g, '\\$&')}]`) as Input<`[${EscapeChar<T>}]`>
14+
function createCharInput<T extends string>(raw: T) {
15+
const input = createInput(`[${raw}]`)
16+
const from = <From extends string, To extends string>(charFrom: From, charTo: To) => createCharInput(`${raw}${escapeCharInput(charFrom)}-${escapeCharInput(charTo)}`)
17+
const orChar = Object.assign(<T extends string>(chars: T) => createCharInput(`${raw}${escapeCharInput(chars)}`), { from })
18+
return Object.assign(input, { orChar, from }) as CharInput<T>
1719
}
1820

19-
/** This matches any character that is not in the string provided */
20-
export function charNotIn<T extends string>(chars: T) {
21-
return createInput(`[^${chars.replace(/[-\\^\]]/g, '\\$&')}]`) as Input<`[^${EscapeChar<T>}]`>
21+
function escapeCharInput<T extends string>(raw: T) {
22+
return raw.replace(/[-\\^\]]/g, '\\$&') as EscapeChar<T>
2223
}
2324

25+
/** This matches any character in the string provided */
26+
export const charIn = Object.assign(<T extends string>(chars: T) => {
27+
return createCharInput(escapeCharInput(chars))
28+
}, createCharInput(''))
29+
30+
/** This matches any character that is not in the string provided */
31+
export const charNotIn = Object.assign(<T extends string>(chars: T) => {
32+
return createCharInput(`^${escapeCharInput(chars)}`)
33+
}, createCharInput('^'))
34+
2435
/**
2536
* This takes a variable number of inputs and matches any of them
2637
* @example

src/core/internal.ts

+6
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { EscapeChar } from './types/escape'
12
import type { Join } from './types/join'
23
import type { InputSource, MapToCapturedGroupsArr, MapToGroups, MapToValues } from './types/sources'
34
import type { IfUnwrapped } from './wrap'
@@ -135,6 +136,11 @@ export interface Input<
135136
toString: () => string
136137
}
137138

139+
export interface CharInput<T extends string> extends Input<`[${T}]`> {
140+
orChar: (<Or extends string>(chars: Or) => CharInput<`${T}${EscapeChar<Or>}`>) & CharInput<T>
141+
from: <From extends string, To extends string>(charFrom: From, charTo: To) => CharInput<`${T}${EscapeChar<From>}-${EscapeChar<To>}`>
142+
}
143+
138144
export function createInput<
139145
Value extends string,
140146
Groups extends string = never,

test/inputs.test.ts

+30
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,41 @@ describe('inputs', () => {
1212
expect(new RegExp(input as any)).toMatchInlineSnapshot('/\\[fo\\\\\\]\\\\\\^\\]/')
1313
expectTypeOf(extractRegExp(input)).toEqualTypeOf<'[fo\\]\\^]'>()
1414
})
15+
it('charIn.orChar', () => {
16+
const input = charIn('a').orChar('b')
17+
expect(new RegExp(input as any)).toMatchInlineSnapshot('/\\[ab\\]/')
18+
expectTypeOf(extractRegExp(input)).toEqualTypeOf<'[ab]'>()
19+
})
20+
it('charIn.orChar.from', () => {
21+
const input = charIn('a').orChar.from('a', 'b')
22+
expect(new RegExp(input as any)).toMatchInlineSnapshot('/\\[aa-b\\]/')
23+
expectTypeOf(extractRegExp(input)).toEqualTypeOf<'[aa-b]'>()
24+
})
25+
it('charIn.from', () => {
26+
const input = charIn.from('a', 'b')
27+
expect(new RegExp(input as any)).toMatchInlineSnapshot('/\\[a-b\\]/')
28+
expectTypeOf(extractRegExp(input)).toEqualTypeOf<'[a-b]'>()
29+
})
1530
it('charNotIn', () => {
1631
const input = charNotIn('fo^-')
1732
expect(new RegExp(input as any)).toMatchInlineSnapshot('/\\[\\^fo\\\\\\^\\\\-\\]/')
1833
expectTypeOf(extractRegExp(input)).toEqualTypeOf<'[^fo\\^\\-]'>()
1934
})
35+
it('charNotIn.orChar', () => {
36+
const input = charNotIn('a').orChar('b')
37+
expect(new RegExp(input as any)).toMatchInlineSnapshot('/\\[\\^ab\\]/')
38+
expectTypeOf(extractRegExp(input)).toEqualTypeOf<'[^ab]'>()
39+
})
40+
it('charNotIn.orChar.from', () => {
41+
const input = charNotIn('a').orChar.from('a', 'b')
42+
expect(new RegExp(input as any)).toMatchInlineSnapshot('/\\[\\^aa-b\\]/')
43+
expectTypeOf(extractRegExp(input)).toEqualTypeOf<'[^aa-b]'>()
44+
})
45+
it('charNotIn.from', () => {
46+
const input = charNotIn.from('a', 'b')
47+
expect(new RegExp(input as any)).toMatchInlineSnapshot('/\\[\\^a-b\\]/')
48+
expectTypeOf(extractRegExp(input)).toEqualTypeOf<'[^a-b]'>()
49+
})
2050
it('anyOf', () => {
2151
const values = ['fo/o', 'bar', 'baz', oneOrMore('this')] as const
2252
const input = anyOf(...values)

0 commit comments

Comments
 (0)