demahom18 / magic-regexp

A compiled-away, type-safe, readable RegExp alternative

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

🦄 magic-regexp

npm version npm downloads Github Actions Codecov LGTM

A compiled-away, type-safe, readable RegExp alternative

Features

⚠️ magic-regexp is currently a work in progress. ⚠️

  • 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, oneOrMore
  • All chainable with and, or, after, before, notAfter, notBefore, times, as, at, optionally

Future ideas

  • More TypeScript guard-rails
  • More complex RegExp features/syntax
  • Instrumentation for accurately getting coverage on RegExps
  • Hybrid/partially-compiled RegExps for better dynamic support

Setup

Install package:

# npm
npm install magic-regexp

# yarn
yarn add magic-regexp

# pnpm
pnpm install magic-regexp
import { createRegExp, exactly } from 'magic-regexp'

const regExp = createRegExp(exactly('foo/test.js').after('bar/'))
console.log(regExp)

// /(?<=bar\/)foo\/test\.js/

Usage

Every pattern you create with the library should be wrapped in createRegExp. It also takes a second argument, which is an array of flags.

import { createRegExp, global, multiline } from 'magic-regexp'
createRegExp('string-to-match', [global, multiline])
// you can also pass flags directly as strings
createRegExp('string-to-match', ['g', 'm'])

Note By default, all helpers from magic-regexp assume that input that is passed should be escaped - so no special RegExp characters apply. So createRegExp('foo?\d') will not match food3 but only foo?\d exactly.

There are a range of helpers that can be used to activate pattern matching, and they can be chained.

They are:

  • charIn, charNotIn - this matches or doesn't match any character in the string provided.
  • anyOf - this takes an array of inputs and matches any of them.
  • 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 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.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().

Compilation at build

The best way to use magic-regexp is by making use of the included transform.

const regExp = createRegExp(exactly('foo/test.js').after('bar/'))
// => gets _compiled_ to
const regExp = /(?<=bar\/)foo\/test\.js/

Of course, this only works with non-dynamic regexps. Within the createRegExp block you have to include all the helpers you are using from magic-regexp - and not rely on any external variables. This, for example, will not statically compile into a RegExp, although it will still continue to work with a minimal runtime:

const someString = 'test'
const regExp = createRegExp(exactly(someString))

Nuxt

import { defineNuxtConfig } from 'nuxt'

// https://v3.nuxtjs.org/api/configuration/nuxt.config
export default defineNuxtConfig({
  // This will also enable auto-imports of magic-regexp helpers
  modules: ['magic-regexp/nuxt'],
})

Vite

import { defineConfig } from 'vite'
import { MagicRegExpTransformPlugin } from 'magic-regexp/transform'

export default defineConfig({
  plugins: [MagicRegExpTransformPlugin.vite()],
})

Next.js

For Next, you will need to ensure you are using next.config.mjs or have set "type": "module" in your `package.json.

import { MagicRegExpTransformPlugin } from 'magic-regexp/transform'

export default {
  webpack(config) {
    config.plugins = config.plugins || []
    config.plugins.push(MagicRegExpTransformPlugin.webpack())
    return config
  },
}

unbuild

import { defineBuildConfig } from 'unbuild'
import { MagicRegExpTransformPlugin } from 'magic-regexp/transform'

export default defineBuildConfig({
  hooks: {
    'rollup:options': (options, config) => {
      config.plugins.push(MagicRegExpTransformPlugin.rollup())
    },
  },
})

Examples

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
  • Enable Corepack using corepack enable (use npm i -g corepack for Node.js < 16.10)
  • Install dependencies using pnpm install
  • Run interactive tests using pnpm dev

Similar packages

License

Made with ❤️

Published under MIT License.

About

A compiled-away, type-safe, readable RegExp alternative

License:MIT License


Languages

Language:TypeScript 91.7%Language:JavaScript 8.0%Language:Shell 0.2%