diff --git a/src/core/inputs.ts b/src/core/inputs.ts index 9380f1b9..37722250 100644 --- a/src/core/inputs.ts +++ b/src/core/inputs.ts @@ -1,4 +1,4 @@ -import type { Input } from './internal' +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' @@ -11,16 +11,27 @@ export type { Input } const ESCAPE_REPLACE_RE = /[.*+?^${}()|[\]\\/]/g -/** This matches any character in the string provided */ -export function charIn(chars: T) { - return createInput(`[${chars.replace(/[-\\^\]]/g, '\\$&')}]`) as Input<`[${EscapeChar}]`> +function createCharInput(raw: T) { + const input = createInput(`[${raw}]`) + const from = (charFrom: From, charTo: To) => createCharInput(`${raw}${escapeCharInput(charFrom)}-${escapeCharInput(charTo)}`) + const orChar = Object.assign((chars: T) => createCharInput(`${raw}${escapeCharInput(chars)}`), { from }) + return Object.assign(input, { orChar, from }) as CharInput } -/** This matches any character that is not in the string provided */ -export function charNotIn(chars: T) { - return createInput(`[^${chars.replace(/[-\\^\]]/g, '\\$&')}]`) as Input<`[^${EscapeChar}]`> +function escapeCharInput(raw: T) { + return raw.replace(/[-\\^\]]/g, '\\$&') as EscapeChar } +/** This matches any character in the string provided */ +export const charIn = Object.assign((chars: T) => { + return createCharInput(escapeCharInput(chars)) +}, createCharInput('')) + +/** This matches any character that is not in the string provided */ +export const charNotIn = Object.assign((chars: T) => { + return createCharInput(`^${escapeCharInput(chars)}`) +}, createCharInput('^')) + /** * This takes a variable number of inputs and matches any of them * @example diff --git a/src/core/internal.ts b/src/core/internal.ts index 8944bd09..1f9bc87a 100644 --- a/src/core/internal.ts +++ b/src/core/internal.ts @@ -1,3 +1,4 @@ +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' @@ -135,6 +136,11 @@ export interface Input< toString: () => string } +export interface CharInput extends Input<`[${T}]`> { + orChar: ((chars: Or) => CharInput<`${T}${EscapeChar}`>) & CharInput + from: (charFrom: From, charTo: To) => CharInput<`${T}${EscapeChar}-${EscapeChar}`> +} + export function createInput< Value extends string, Groups extends string = never, diff --git a/test/inputs.test.ts b/test/inputs.test.ts index 277098ac..52288579 100644 --- a/test/inputs.test.ts +++ b/test/inputs.test.ts @@ -12,11 +12,41 @@ describe('inputs', () => { expect(new RegExp(input as any)).toMatchInlineSnapshot('/\\[fo\\\\\\]\\\\\\^\\]/') expectTypeOf(extractRegExp(input)).toEqualTypeOf<'[fo\\]\\^]'>() }) + it('charIn.orChar', () => { + const input = charIn('a').orChar('b') + expect(new RegExp(input as any)).toMatchInlineSnapshot('/\\[ab\\]/') + expectTypeOf(extractRegExp(input)).toEqualTypeOf<'[ab]'>() + }) + it('charIn.orChar.from', () => { + const input = charIn('a').orChar.from('a', 'b') + expect(new RegExp(input as any)).toMatchInlineSnapshot('/\\[aa-b\\]/') + expectTypeOf(extractRegExp(input)).toEqualTypeOf<'[aa-b]'>() + }) + it('charIn.from', () => { + const input = charIn.from('a', 'b') + expect(new RegExp(input as any)).toMatchInlineSnapshot('/\\[a-b\\]/') + expectTypeOf(extractRegExp(input)).toEqualTypeOf<'[a-b]'>() + }) it('charNotIn', () => { const input = charNotIn('fo^-') expect(new RegExp(input as any)).toMatchInlineSnapshot('/\\[\\^fo\\\\\\^\\\\-\\]/') expectTypeOf(extractRegExp(input)).toEqualTypeOf<'[^fo\\^\\-]'>() }) + it('charNotIn.orChar', () => { + const input = charNotIn('a').orChar('b') + expect(new RegExp(input as any)).toMatchInlineSnapshot('/\\[\\^ab\\]/') + expectTypeOf(extractRegExp(input)).toEqualTypeOf<'[^ab]'>() + }) + it('charNotIn.orChar.from', () => { + const input = charNotIn('a').orChar.from('a', 'b') + expect(new RegExp(input as any)).toMatchInlineSnapshot('/\\[\\^aa-b\\]/') + expectTypeOf(extractRegExp(input)).toEqualTypeOf<'[^aa-b]'>() + }) + it('charNotIn.from', () => { + const input = charNotIn.from('a', 'b') + expect(new RegExp(input as any)).toMatchInlineSnapshot('/\\[\\^a-b\\]/') + expectTypeOf(extractRegExp(input)).toEqualTypeOf<'[^a-b]'>() + }) it('anyOf', () => { const values = ['fo/o', 'bar', 'baz', oneOrMore('this')] as const const input = anyOf(...values)