Skip to content

Commit

Permalink
feat: add optionally, oneOrMore and times.any and times.atLeast
Browse files Browse the repository at this point in the history
  • Loading branch information
danielroe committed Jul 17, 2022
1 parent b33b093 commit 0484b31
Show file tree
Hide file tree
Showing 3 changed files with 32 additions and 3 deletions.
24 changes: 21 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@
- Runtime is zero-dependency and ultra-minimal
- Ships with transform for compiling runtime to pure RegExp
- Supports automatically typed capture groups
- Packed with useful utilities: `charIn`, `charNotIn`, `anyOf`, `char`, `word`, `digit`, `whitespace`, `letter`, `tab`, `linefeed`, `carriageReturn`, `not`, `maybe`, `exactly`
- All chainable with `and`, `or`, `after`, `before`, `notAfter`, `notBefore`, `times`, `as`, `at`
- Packed with useful utilities: `charIn`, `charNotIn`, `anyOf`, `char`, `word`, `digit`, `whitespace`, `letter`, `tab`, `linefeed`, `carriageReturn`, `not`, `maybe`, `exactly`, `oneOrMore`
- All chainable with `and`, `or`, `after`, `before`, `notAfter`, `notBefore`, `times`, `as`, `at`, `optionally`

**Future ideas**

Expand Down Expand Up @@ -75,14 +75,16 @@ They are:
- `char`, `word`, `digit`, `whitespace`, `letter`, `tab`, `linefeed` and `carriageReturn` - these are helpers for specific RegExp characters.
- `not` - this can prefix `word`, `digit`, `whitespace`, `letter`, `tab`, `linefeed` or `carriageReturn`. For example `createRegExp(not.letter)`.
- `maybe` - equivalent to `?` - this marks the input as optional.
- `oneOrMore` - equivalent to `+` - this marks the input as repeatable, any number of times but at least once.
- `exactly` - this escapes a string input to match it exactly.

All of these helpers return an object of type `Input` that can be chained with the following helpers:

- `and` - this adds a new pattern to the current input.
- `or` - this provides an alternative to the current input.
- `after`, `before`, `notAfter` and `notBefore` - these activate positive/negative lookahead/lookbehinds. 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).
- `times` - this is a function you can call directly to repeat the previous pattern an exact number of times, or you can use `times.between(min, max)` to specify a range.
- `times` - this is a function you can call directly to repeat the previous pattern an exact number of times, or you can use `times.between(min, max)` to specify a range, `times.atLeast(num)` to indicate it must repeat x times or `times.any()` to indicate it can repeat any number of times, _including none_.
- `optionally` - this is a function you can call to mark the current input as optional.
- `as` - 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()`.
- `at` - this allows you to match beginning/ends of lines with `at.lineStart()` and `at.lineEnd()`.

Expand Down Expand Up @@ -157,6 +159,22 @@ export default defineBuildConfig({
})
```

## Examples

```js
import { createRegExp, exactly, oneOrMore, digit } from 'magic-regexp'

// Quick-and-dirty semver
createRegExp(
oneOrMore(digit)
.as('major')
.and('.')
.and(oneOrMore(digit).as('minor'))
.and(exactly('.').and(oneOrMore(char).as('patch')).optionally())
)
// /(?<major>(\d)+)\.(?<minor>(\d)+)(\.(?<patch>(.)+))?/
```

## 💻 Development

- Clone this repository
Expand Down
8 changes: 8 additions & 0 deletions src/core/inputs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ export interface Input<T extends string = never> {
(number: number): Input<T>
/** specify a range of times to repeat the previous pattern */
between: (min: number, max: number) => Input<T>
/** specify that the expression can repeat any number of times, _including none_ */
any: () => Input<T>
/** specify that the expression must occur at least x times */
atLeast: (min: number) => Input<T>
}
/** 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()` */
as: <K extends string>(key: K) => Input<T | K>
Expand All @@ -26,6 +30,8 @@ export interface Input<T extends string = never> {
lineStart: () => Input<T>
lineEnd: () => Input<T>
}
/** this allows you to mark the input so far as optional */
optionally: () => Input<T>
toString: () => string
}

Expand Down Expand Up @@ -61,3 +67,5 @@ export const maybe = (str: string | Input) => createInput(`(${exactly(str)})?`)
/** This escapes a string input to match it exactly */
export const exactly = (str: string | Input) =>
typeof str === 'string' ? createInput(str.replace(/[.*+?^${}()|[\]\\/]/g, '\\$&')) : str
export const oneOrMore = (str: string | Input) => createInput(`(${exactly(str)})+`)
// export const = (str: string | Input) => createInput(`(${exactly(str)})+`)
3 changes: 3 additions & 0 deletions src/core/internal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,11 @@ export const createInput = <T extends string = never>(s: string | Input<T>): Inp
notAfter: input => createInput(`(?<!${exactly(input)})${s}`),
notBefore: input => createInput(`${s}(?!${exactly(input)})`),
times: Object.assign((number: number) => createInput(`(${s}){${number}}`), {
any: () => createInput(`(${s})*`),
atLeast: (min: number) => createInput(`(${s}){${min},}`),
between: (min: number, max: number) => createInput(`(${s}){${min},${max}}`),
}),
optionally: () => createInput(`(${s})?`),
as: key => createInput(`(?<${key}>${s})`),
at: {
lineStart: () => createInput(`^${s}`),
Expand Down

0 comments on commit 0484b31

Please sign in to comment.