{"id":15833558,"url":"https://github.com/discretetom/r-compose","last_synced_at":"2025-04-01T12:14:50.777Z","repository":{"id":206397513,"uuid":"716533951","full_name":"DiscreteTom/r-compose","owner":"DiscreteTom","description":"Compose RegExp in JavaScript in a readable and maintainable way.","archived":false,"fork":false,"pushed_at":"2023-12-27T13:43:54.000Z","size":90,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2024-10-12T13:31:23.666Z","etag":null,"topics":["javascript","r-compose","regex","regex-builder","regexp","typescript"],"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/DiscreteTom.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"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":"2023-11-09T10:38:13.000Z","updated_at":"2023-11-09T11:23:21.000Z","dependencies_parsed_at":"2023-11-09T13:05:23.431Z","dependency_job_id":"8592ad4d-6ffc-4574-911f-e09ef85b6ead","html_url":"https://github.com/DiscreteTom/r-compose","commit_stats":{"total_commits":28,"total_committers":1,"mean_commits":28.0,"dds":0.0,"last_synced_commit":"193231b7a766d3809c91dea85ac58171806cb0b1"},"previous_names":["discretetom/r-compose"],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DiscreteTom%2Fr-compose","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DiscreteTom%2Fr-compose/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DiscreteTom%2Fr-compose/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DiscreteTom%2Fr-compose/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/DiscreteTom","download_url":"https://codeload.github.com/DiscreteTom/r-compose/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246635986,"owners_count":20809333,"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":["javascript","r-compose","regex","regex-builder","regexp","typescript"],"created_at":"2024-10-05T13:20:24.686Z","updated_at":"2025-04-01T12:14:50.728Z","avatar_url":"https://github.com/DiscreteTom.png","language":"TypeScript","readme":"# R-Compose\n\n[![npm](https://img.shields.io/npm/v/@discretetom/r-compose?style=flat-square)](https://www.npmjs.com/package/@discretetom/r-compose)\n![coverage](https://img.shields.io/codecov/c/github/DiscreteTom/r-compose?style=flat-square)\n![build](https://img.shields.io/github/actions/workflow/status/DiscreteTom/r-compose/publish.yml?style=flat-square)\n![license](https://img.shields.io/github/license/DiscreteTom/r-compose?style=flat-square)\n\nCompose `RegExp` in JavaScript in a readable and maintainable way.\n\n## Install\n\n```bash\nyarn add @discretetom/r-compose\n```\n\nOr try it online in the [playground](https://dttk.discretetom.com/js-playground?crushed=%28%27dependencieV%27https%253A%252F%252Fcdn.jsdelivr.net%252Fnpm%252F%2540discUtetom%252FX%25400.2.2%252Fdist%252FX.min.js%27%255D%7EcellVMPUparKXJZcEK%29LrCEeAAHor%2520usKN%2520diUctlyAqsZN%2520%29LrCEeStrue%7Eid%210_MCEKRegexJt%2520rLcEe%257BQHcEKthKzbody%2520with%2520thKNQ%257B%28%2520escapeBqcatBselectBgroupBcaptuUBanyBsomeYBlookaheadBlookbehindBnotBraw%2520%29%257D%2520W%28Q*Uturn%2520qcat%257BQ**%252FGn%252FDFGGnFQ**F123FDF123FQ**falsK%253F%2520%252F123%252F%2520%253A%2520FFDignoUdQ**escape%257BF%257B%257DF%257DDFGG%257BGG%257DFQ*%257DQ_QHzflagsYQ%2522g%2522A%257D%253BIT_MOutputJole.log%257Br%257DI683287010%29%255D%7EpanelV1703T%255D%29*%2520%2520%2520%2520A%255Cr%255CnB%252C%2520DBHWEomposF%255C%27G%255C%255CH%252F%252F%2520ISfalse%7Eid%211703J%27%7Ecode%21%27qsKe%2520L%2520%253D%2520M%28%27name%21%27NcEablesQA*S%27%7EUadonly%21T340940582UreVs%21%255BW%253D%253E%2520Xr-cEeYBoptionalZt%2520%28%2520_%29%252CqconzUgex%2520%2501zq_ZYXWVUTSQNMLKJIHGFEDBA*_).\n\n## Features\n\n- Treat `RegExp` as its source string, and concat it with literal or escaped strings. Empty strings will be ignored.\n\n```ts\nconcat(\n  /\\n/, // =\u003e \"\\\\n\"\n  \"123\", // =\u003e \"123\"\n  false ? /123/ : \"\", // =\u003e ignored\n  escape(\"()\"), // =\u003e \"\\\\(\\\\)\"\n) === \"\\\\n123\\\\(\\\\)\";\n```\n\n- Auto group content in a non-capturing group if the composable function is context-sensitive.\n\n```ts\nselect(\"a\", \"b\") === \"(?:a|b)\";\noptional(\"c\") === \"(?:c)?\";\nlookahead(\"d\") === \"(?=d)\";\n```\n\n- Additional options for some composable functions.\n\n```ts\ncapture(\"a\", { name: \"foo\" }) === \"(?\u003cfoo\u003ea)\";\noptional(\"a\", { greedy: false }) === \"(?:a)??\";\nlookahead(\"a\", { negative: true }) === \"(?!a)\";\n```\n\n- Composable functions return `string` so you can cascade / nest them.\n\n```ts\nlookahead(\n  concat(\n    lookbehind(\"[_$[:alnum:]]\", { negative: true }),\n    select(lookbehind(/\\.\\.\\./), lookbehind(/\\./, { negative: true })),\n    optional(concat(capture(/\\bexport/), /\\s+/)),\n    optional(concat(capture(/\\bdeclare/), /\\s+/)),\n    concat(/\\b/, capture(select(\"var\", \"let\"))),\n    lookahead(\"[_$[:alnum:]]\", { negative: true }),\n    select(lookahead(/\\.\\.\\./), lookahead(/\\./, { negative: true })),\n  ),\n);\n```\n\n## When to Use\n\n- When to use `concat`? Should I always use `concat('a', 'b', 'c')` instead of directly `/abc/`?\n  - Use `concat` when you need to concat strings with `RegExp`, since the escaped sequences in strings and in `RegExp`s are different. E.g. `concat('a', /\\*/) === \"a\\\\*\"`.\n  - Use `concat` if you have conditional logic, since empty strings will be ignored. E.g. `concat('a', false ? 'b' : '', 'c') === \"ac\"`.\n- When to use those which will create groups like `select/capture/optional/lookahead`?\n  - They are always recommended to use, since parentheses in TypeScript can be organized by code formatter and is more readable than in `RegExp` source string.\n  - Unless the decorated content is only one character, e.g. transform `(?:ab)?` into `optional('ab')` is recommended since the parentheses can be removed, but `a?` is not since there is no parentheses.\n\nOverall, it's your choice to use r-compose aggressively or conservatively. But you can always optimize the organization of your `RegExp` progressively.\n\n## Example\n\n[retsac](https://github.com/DiscreteTom/retsac) use `r-compose` to avoid escape hell and make the code more readable and maintainable.\n\n\u003cdetails\u003e\n\u003csummary\u003eClick to Expand\u003c/summary\u003e\n\n\u003ctable\u003e\n\u003ctr\u003e\u003ctd\u003e Before: string interpolation \u003c/td\u003e\u003ctd\u003e After: r-compose \u003c/td\u003e\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\n\n```ts\nnew RegExp(\n  // open quote\n  `(?:${esc4regex(open)})` +\n    // content, non-greedy\n    `(?:${\n      escape\n        ? `(?:${lineContinuation ? \"\\\\\\\\\\\\n|\" : \"\"}\\\\\\\\.|[^\\\\\\\\${\n            multiline ? \"\" : \"\\\\n\"\n          }])` // exclude `\\n` if not multiline\n        : `(?:${lineContinuation ? \"\\\\\\\\\\\\n|\" : \"\"}.${\n            multiline\n              ? // if multiline, accept `\\n`\n                \"|\\\\n\"\n              : \"\"\n          })`\n    }*?)` + // '*?' means non-greedy(lazy)\n    // close quote\n    `(?:${\n      acceptUnclosed\n        ? // if accept unclosed, accept '$'(EOF)\n          // or '\\n'(if not multiline)\n          `(?:${esc4regex(close)})|$${multiline ? \"\" : \"|(?=\\\\n)\"}`\n        : esc4regex(close)\n    })`,\n);\n```\n\n\u003c/td\u003e\n\u003ctd\u003e\n\n```ts\ncompose(({ concat, any, select, lookahead, escape, not }) =\u003e\n  concat(\n    // match open quote\n    escape(open),\n    // match content\n    any(\n      escaped\n        ? select(\n            lineContinuation ? /\\\\\\n/ : \"\", // line continuation is treated as part of the content\n            /\\\\./, // any escaped character is treated as part of the content\n            not(\n              // any character except the following is treated as part of the content\n              concat(\n                /\\\\/, // standalone backslash shouldn't be treated as part of the content\n                multiline ? \"\" : /\\n/, // if not multiline, `\\n` shouldn't be treated as part of the content\n              ),\n            ),\n          )\n        : select(\n            lineContinuation ? /\\\\\\n/ : \"\", // line continuation is treated as part of the content\n            /./, // any non-newline character is treated as part of the content\n            multiline ? /\\n/ : \"\", // if multiline, `\\n` should be treated as part of the content\n          ),\n      // since we use `/./` in the content, we need to make sure it doesn't match the close quote\n      { greedy: false },\n    ),\n    // match close quote\n    acceptUnclosed\n      ? select(\n          escape(close),\n          \"$\", // unclosed string is acceptable, so EOF is acceptable\n          multiline\n            ? \"\" // if multiline is enabled, we don't treat `\\n` as the close quote\n            : lookahead(/\\n/), // use lookahead so we don't include the `\\n` in the result\n        )\n      : escape(close), // unclosed string is not accepted, so we only accept the close quote\n  ),\n);\n```\n\n\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/table\u003e\n\n\u003e Source: https://github.com/DiscreteTom/retsac/commit/86b7ddf4a8c008086171b8d471dd05214327bfb3?diff=split\n\n\u003c/details\u003e\n\n[retsac](https://github.com/DiscreteTom/retsac) also use `r-compose` to refactor long and complex regex with confidence.\n\n\u003cdetails\u003e\n\u003csummary\u003eClick to Expand\u003c/summary\u003e\n\n### Before\n\n```ts\nenableSeparator\n  ? new RegExp(\n      `(?:0x[\\\\da-f]+|0o[0-7]+|\\\\d+(?:${separator}\\\\d+)*(?:\\\\.\\\\d+(?:${separator}\\\\d+)*)?(?:[eE][-+]?\\\\d+(?:${separator}\\\\d+)*)?)${\n        boundary ? \"\\\\b(?!\\\\.)\" : \"\" // '.' is not allowed as the boundary\n      }`,\n      \"i\",\n    )\n  : new RegExp(\n      `(?:0x[\\\\da-f]+|0o[0-7]+|\\\\d+(?:\\\\.\\\\d+)?(?:[eE][-+]?\\\\d+)?)${\n        boundary ? \"\\\\b(?!\\\\.)\" : \"\" // '.' is not allowed as the boundary\n      }`,\n      \"i\",\n    );\n```\n\n### After\n\n```ts\ncompose(\n  ({ concat, select, any, optional, lookahead }) =\u003e {\n    const separatorPart = enableSeparator ? any(concat(separator, /\\d+/)) : \"\";\n    return concat(\n      select(\n        /0x[\\da-f]+/, // hexadecimal\n        /0o[0-7]+/, // octal\n        // below is decimal with separator\n        concat(\n          /\\d+/, // integer part\n          separatorPart, // separator and additional integer part\n          optional(concat(/\\.\\d+/, separatorPart)), // decimal part\n          optional(concat(/[eE][-+]?\\d+/, separatorPart)), // exponent part\n        ),\n      ),\n      boundary\n        ? concat(\n            /\\b/,\n            // '.' match /\\b/ but is not allowed as the boundary\n            lookahead(/\\./, { negative: true }),\n          )\n        : \"\",\n    );\n  },\n  \"i\", // case insensitive\n);\n```\n\n\u003e Source: https://github.com/DiscreteTom/retsac/commit/430a2175eb4c6d564ebdacf5b01a91ea42885ef2?diff=split\n\n\u003c/details\u003e\n\n## [More Examples](https://github.com/DiscreteTom/r-compose/tree/main/examples)\n\n## [CHANGELOG](https://github.com/DiscreteTom/r-compose/blob/main/CHANGELOG.md)\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdiscretetom%2Fr-compose","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdiscretetom%2Fr-compose","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdiscretetom%2Fr-compose/lists"}