{"id":15608561,"url":"https://github.com/hypercubed/lit-regex","last_synced_at":"2025-04-28T11:46:16.754Z","repository":{"id":38315176,"uuid":"308773723","full_name":"Hypercubed/lit-regex","owner":"Hypercubed","description":"Template literals regular expressions for JavaScript","archived":false,"fork":false,"pushed_at":"2023-08-09T12:34:16.000Z","size":248,"stargazers_count":4,"open_issues_count":13,"forks_count":1,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-13T07:48:57.109Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Hypercubed.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":".github/CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-10-31T00:32:01.000Z","updated_at":"2022-06-06T20:41:36.000Z","dependencies_parsed_at":"2024-06-21T02:15:24.613Z","dependency_job_id":"e5fe2af4-31e3-442e-bf31-4105310638d8","html_url":"https://github.com/Hypercubed/lit-regex","commit_stats":{"total_commits":33,"total_committers":3,"mean_commits":11.0,"dds":0.5151515151515151,"last_synced_commit":"3a8cfda6b427ca8f1317550def3d9f417c9f77fa"},"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Hypercubed%2Flit-regex","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Hypercubed%2Flit-regex/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Hypercubed%2Flit-regex/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Hypercubed%2Flit-regex/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Hypercubed","download_url":"https://codeload.github.com/Hypercubed/lit-regex/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251309121,"owners_count":21568749,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-03T05:21:25.131Z","updated_at":"2025-04-28T11:46:16.736Z","avatar_url":"https://github.com/Hypercubed.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# lit-regex\n\nTemplate literals regular expressions for JavaScript\n\n## Overview\n\nThere are a few existing JavaScript tools for composing regular expressions (see, for example, [re-template-tag](https://github.com/rauschma/re-template-tag) and [regexp-make-js](https://github.com/mikesamuel/regexp-make-js)).  However, all of these assume the users is writing primarily regex, treating literal strings as an afterthought.  There are many cases were most of the text will be plaintext which may require escaping.  For example, if I wanted to match the string \"Apples for $0.99 (per lb)\" I would need the regular expression `/Apples for \\$0\\.99 \\[per #\\]/`.  Did I forget to escape anything?  Start trying to compose a regex using strings and the problem is exasperated.\n\n`lit-regex` lets you compose coherent regular expressions in JavaScript using [template literals](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals). `lit-regex` templates are plain text strings that allow embedded regular expressions for composability.\n\n## Installation\n\n```sh\nnpm i lit-regex\n```\n\n## Usage\n\nExpanding on the example above; suppose we want to match the string \"We sell (XXX) for $YY.YY [per #]\" were `XXX` can be `/apples/i` or `/oranges/i` (case-insensitive) inside a named capture group and `YY.YY` is any price inside.  Maybe you're sufficiently familiar with regular expressions to write:\n\n```ts\nconst re = /We sell (?\u003cproducts\u003e(?:[Aa][Pp][Pp][Ll][Ee][Ss]|[Oo][Rr][Aa][Nn][Gg][Ee][Ss])) for \\$\\d+\\.\\d{2} \\[per #\\]\\./;\n```\n\nNot exactly readable.  Not lets try doing the same via composition:\n\n```ts\nconst digit = /\\d/;\nconst price = `\\\\$${digit.source}+\\\\.${digit.source}{2}`;\nconst products = /(?:[Aa][Pp][Pp][Ll][Ee][Ss]|[Oo][Rr][Aa][Nn][Gg][Ee][Ss])/;\nconst re = new RegExp(`We sell (?\u003cproducts\u003e${products.source}) for ${price} \\\\[per #\\\\]\\\\.`);\n```\n\nNotice a few difficulties here:\n\n* Since `digit` and `products` are RegExp objects we need to use `.source` to get the source regexp.\n* Making `products` case insensitive is a pain.\n* Named capture groups decrease readability and appear redundant.\n* Exact text matches need to be escaped.\n* Regular expressions within a string need to be double escaped.\n\nUsing `lit-regex` we could write:\n\n```js\nimport { regex, oneOrMore, repeat } from 'lit-regex';\n\nconst digit = /\\d/;\nconst price = regex`$${oneOrMore(digit)}.${repeat(digit, 2)}`;\nconst products = [/apples/i, /oranges/i];\nconst re = regex`We sell ${{ products }} for ${price} [per #].`;\n// same as /We sell (?\u003cproducts\u003e(?:[Aa][Pp][Pp][Ll][Ee][Ss]|[Oo][Rr][Aa][Nn][Gg][Ee][Ss])) for \\$\\d+\\.\\d{2} \\[per #\\]\\./;\n```\n\nA few rules to notice:\n\n* If an expression is a `RegExp`, the regular expression is embedded as a substring match (`i` flag is preserved or converted to inline case-insensitive).\n* If an expression is an `Array`, it is treated as an alternation (OR operand) and each item within the array is treated with these same rules.\n* If an expression is an object with one key, it is treated as a capture group where the key is the name (keys prefixed with a `$` are unnamed) and the value is treated with these same rules.\n* All other expressions (strings and numbers) are escaped for literal matching; this includes the static portions of the template string.\n\nWhile the ignore case flag (`i`) is propagated during composition; other flags are ignored.  To set flags on `regex` output use the following syntax:\n\n```js\nregex.gi`Hello World`;\n// same as /Hello World/gi\n\nregex.m`${/^/}Hello World${/$/}`;\n// same as /^Hello World$/m\n```\n\n## Functional API\n\nMost of the power of `lit-regex` is in the `regex` template tag; which is, effectively, sugar for a set of composable functions for building regular expressions.  These functions can be used by themselves to compose regular expressions or within `regex` template tag expressions.  These functions, in general, follow the same the rules listed above; again:\n\n* `RegExp`s are treated as a regular expression.\n* Arrays are treated as an alternation.\n* Objects are capture groups.\n* Everything else is escaped for a literal match.\n\nThe example above can be rewritten using the composition functions:\n\n```js\nimport { seq, oneOrMore, repeat, anyOf, capture } from 'lit-regex';\n\nconst digit = /\\d/;\nconst price = seq('$', oneOrMore(digit), '.', repeat(digit, 2));\nconst products = anyOf(/apples/i, /oranges/i);\nconst re = seq('We sell ', capture(products, 'products'), ' for ', price, ' [per #].');\n```\n\nor a combination of functions and tagged templates:\n\n```js\nimport { regex, seq, oneOrMore, repeat, anyOf } from 'lit-regex';\n\nconst digit = /\\d/;\nconst price = seq('$', oneOrMore(digit), '.', repeat(digit, 2));\nconst products = anyOf(/apples/i, /oranges/i);\nconst re = regex`We sell ${{products}} for ${price} [per #].`;\n```\n\nEach function is explained below:\n\n### `seq(...arg)`\n\nEach argument is treated by the expression rules listed above and combined into a RegExp sequence.\n\n```js\nseq('a', 'b', /c/);\n// same as /abc/\n\nseq('Hello', ' ', /[Ww]orld/)\n// same as /Hello [Ww]orld/\n\nseq('Hello', ' ', [/[Ww]orld/, 'Earth'])\n// same as /Hello (?:[Ww]orld|Earth)/\n```\n\n### `anyOf(...arg)`\n\nEach argument is treated by the expression rules listed above and combined into a RegExp alternation.\n\n```js\nanyOf('a', 'b', /c/)\n// same as /[abc]/\n\nanyOf(/[Ww]orld/, 'Earth')\n// same as /(?:[Ww]orld|Earth)/\n```\n\n### `ahead(arg)`\n\nThe single argument is treated according to the expression rules listed above and returned in a look-ahead group.\n\n```js\nahead('Hello');\n// same as /(?=Hello)/\n\nahead(/[Ww]orld/);\n// same as /(?=[Ww]orld)/\n\nahead([/Hello/, /[Ww]orld/]);\n// same as /(?=(?:Hello|[Ww]orld))/\n```\n\n### `capture(arg, name?)`\n\nThe first argument is treated according to the expression rules listed above and returned in a capture group.  The second argument (if provided) is a name for generating named capture groups. \n\n```js\ncapture('Hello');\n// same as /(Hello)/\n\ncapture(/[Ww]orld/);\n// same as /([Ww]orld)/\n\ncapture(['Hello', /[Ww]orld/], 'greeting');\n// same as /(?\u003cgreeting\u003e(?:Hello|[Ww]orld))/\n```\n\n### `avoid(arg)`\n\nThe single argument is treated according to the expression rules listed above and returned in a negative ahead.\n\n```js\navoid('Hello');\n// same as /(?!Hello)/\n\navoid(/[Ww]orld/);\n// same as /(?![Ww]orld)/\n\navoid(['Hello', /[Ww]orld/]);\n// same as /(?!(?:Hello|[Ww]orld))/\n```\n\n### `optional(arg)`\n\nThe single argument is treated according to the expression rules listed above and returned in a optional group.\n\n```js\navoid('Hello');\n// same as /(?:Hello)?/\n\navoid(/[Ww]orld/);\n// same as /(?:[Ww]orld)?/\n\navoid(['Hello', /[Ww]orld/]);\n// same as /(?:Hello|[Ww]orld)?/\n```\n\n### `oneOrMore(arg)`\n\n```js\noneOrMore('Hello');\n// same as /(?:Hello)+/\n\noneOrMore(/[Ww]orld/);\n// same as /(?:[Ww]orld)+/\n\noneOrMore(['Hello', /[Ww]orld/]);\n// same as /(?:Hello|[Ww]orld)+/\n```\n\n### `zeroOrMore(arg)`\n\n```js\nzeroOrMore('Hello');\n// same as /(?:Hello)*/\n\nzeroOrMore(/[Ww]orld/);\n// same as /(?:[Ww]orld)*/\n\nzeroOrMore(['Hello', /[Ww]orld/]);\n// same as /(?:Hello|[Ww]orld)*/\n```\n\n### `repeat(arg, N?)`\n\n```js\nrepeat('Hello', 2);\n// same as /(?:Hello){2}/\n\nrepeat(/[Ww]orld/, [2, 3]);\n// same as /(?:[Ww]orld){2,3}/\n\nrepeat(['Hello', /[Ww]orld/], [5, Infinity]);\n// same as /(?:Hello|[Ww]orld){5,}/\n```\n\n## More examples\n\n### email\n\n```js\nimport { regex, oneOrMore, repeat } from 'lit-regex';\n\nconst localPart = oneOrMore(/[a-zA-Z0-9._%-]/);\nconst domainPart = oneOrMore(/[a-zA-Z0-9.-]/);\nconst tld = repeat(/[a-zA-Z]/, [2, 24]);\n\nconst re = regex`${localPart}@${domainPart}.${tld}`;\n// same as /[a-zA-Z0-9._%-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,24}/\n```\n\n### URL\n\n```js\nimport { regex, oneOrMore, repeat } from 'lit-regex';\n\nconst scheme = ['http', 'https', 'ftp'];\nconst sub = 'www.';\nconst domainPart = repeat(/[a-zA-Z0-9.-]/, [2, 256]);\nconst tld = repeat(/[a-zA-Z]/, [2, 24]);\na\nconst re = regex`${scheme}://${optional(sub)}${domainPart}.${tld}`;\n// same as /(?:http|https|ftp):\\/\\/(?:www\\.)?[a-zA-Z0-9.-]{2,256}\\.[a-zA-Z]{2,24}/\n```\n\n### Dates with named capture\n\n```js\nimport { regex, oneOrMore, repeat } from 'lit-regex';\n\nconst year = repeat(/\\d/, 4);\nconst month = repeat(/\\d/, 2);\nconst day = repeat(/\\d/, 2);\nconst date = regex`${{ year }}-${{ month }}-${{ day }}`;\n// same as /(?\u003cyear\u003e\\d{4})\\x2d(?\u003cmonth\u003e\\d{2})\\x2d(?\u003cday\u003e\\d{2})/\n```\n\n## Credits and alternatives\n\nThe composable RegExp functional API inspired by [compose-regexp.js](https://github.com/pygy/compose-regexp.js).  Composable regular expressions via template tags inspired by [lit-html](https://lit-html.polymer-project.org/) and [Easy Dynamic Regular Expressions with Tagged Template Literals and Proxies](https://lea.verou.me/2018/06/easy-dynamic-regular-expressions-with-tagged-template-literals-and-proxies/).\n\n## License\n\nThis project is licensed under the MIT License - see the LICENSE file for details\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhypercubed%2Flit-regex","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhypercubed%2Flit-regex","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhypercubed%2Flit-regex/lists"}