diff --git a/.travis.yml b/.travis.yml
index 88a8dbe..ccec9c7 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,3 +1,4 @@
language: node_js
node_js:
+ - 10
- 6
diff --git a/package.json b/package.json
index 7100c2c..6a8576e 100644
--- a/package.json
+++ b/package.json
@@ -2,7 +2,8 @@
"name": "regexparam",
"version": "1.2.2",
"repository": "lukeed/regexparam",
- "description": "A tiny (285B) utility that converts route patterns into RegExp. Limited alternative to `path-to-regexp` 🙇",
+ "description": "A tiny (308B) utility that converts route patterns into RegExp. Limited alternative to `path-to-regexp` 🙇",
+ "unpkg": "dist/regexparam.min.js",
"module": "dist/regexparam.mjs",
"main": "dist/regexparam.js",
"types": "types.d.ts",
diff --git a/readme.md b/readme.md
index 4a0e45e..91a696e 100644
--- a/readme.md
+++ b/readme.md
@@ -1,6 +1,6 @@
-# regexparam [](https://travis-ci.org/lukeed/regexparam)
+# regexparam [](https://travis-ci.org/lukeed/regexparam)
-> A tiny (285B) utility that converts route patterns into RegExp. Limited alternative to [`path-to-regexp`](https://github.com/pillarjs/path-to-regexp) 🙇
+> A tiny (308B) utility that converts route patterns into RegExp. Limited alternative to [`path-to-regexp`](https://github.com/pillarjs/path-to-regexp) 🙇
With `regexparam`, you may turn a pathing string (eg, `/users/:id`) into a regular expression.
@@ -87,12 +87,46 @@ exec('/users/lukeed/repos/new', baz);
> **Important:** When matching/testing against a generated RegExp, your path **must** begin with a leading slash (`"/"`)!
+## Regular Expressions
+
+For fine-tuned control, you may pass a `RegExp` value directly to `regexparam` as its only parameter.
+
+In these situations, `regexparam` **does not** parse nor manipulate your pattern in any way! Because of this, `regexparam` has no "insight" on your route, and instead trusts your input fully. In code, this means that the return value's `keys` is always equal to `false` and the `pattern` is identical to your input value.
+
+This also means that you must manage and parse your own `keys`~!
+You may use [named capture groups](https://javascript.info/regexp-groups#named-groups) or traverse the matched segments manually the "old-fashioned" way:
+
+> **Important:** Please check your target browsers' and target [Node.js runtimes' support](https://node.green/#ES2018-features--RegExp-named-capture-groups)!
+
+```js
+// Named capture group
+const named = regexparam(/^\/posts[/](?[0-9]{4})[/](?[0-9]{2})[/](?[^\/]+)/i);
+const { groups } = named.pattern.exec('/posts/2019/05/hello-world');
+console.log(groups);
+//=> { year: '2019', month: '05', title: 'hello-world' }
+
+// Widely supported / "Old-fashioned"
+const named = regexparam(/^\/posts[/]([0-9]{4})[/]([0-9]{2})[/]([^\/]+)/i);
+const [url, year, month, title] = named.pattern.exec('/posts/2019/05/hello-world');
+console.log(year, month, title);
+//=> 2019 05 hello-world
+```
+
## API
+There are two API variants:
+
+1) When passing a `String` input, the `loose` parameter is able to affect the output. [View API](#regexparamstr-loose)
+
+2) When passing a `RegExp` value, that must be `regexparam`'s _only_ argument.
+Your pattern is saved as written, so `loose` is ignored entirely. [View API](#regexparamrgx)
+
### regexparam(str, loose)
Returns: `Object`
+Returns a `{ keys, pattern }` object, where `pattern` is a generated `RegExp` instance and `keys` is a list of extracted parameter names.
+
#### str
Type: `String`
@@ -117,6 +151,18 @@ rgx('/users/:name').pattern.test('/users/lukeed/repos'); //=> false
rgx('/users/:name', true).pattern.test('/users/lukeed/repos'); //=> true
```
+### regexparam(rgx)
+Returns: `Object`
+
+Returns a `{ keys, pattern }` object, where pattern is _identical_ to your `rgx` and `keys` is `false`, always.
+
+#### rgx
+Type: `RegExp`
+
+Your RegExp pattern.
+
+> **Important:** This pattern is used _as is_! No parsing or interpreting is done on your behalf.
+
## Related
diff --git a/src/index.js b/src/index.js
index db4762b..9f6181a 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,5 +1,6 @@
export default function (str, loose) {
- var c, o, tmp, ext, keys=[], pattern='', arr=str.split('/');
+ if (str instanceof RegExp) return { keys:false, pattern:str };
+ var c, o, tmp, ext, keys=[], pattern='', arr = str.split('/');
arr[0] || arr.shift();
while (tmp = arr.shift()) {
diff --git a/test/index.js b/test/index.js
index 60d2ce6..7e563d0 100644
--- a/test/index.js
+++ b/test/index.js
@@ -1,10 +1,13 @@
const test = require('tape');
const fn = require('../dist/regexparam');
+const hasNamedGroups = 'groups' in /x/.exec('x');
+
function run(route, url, loose) {
let i=0, out={}, result=fn(route, !!loose);
let matches = result.pattern.exec(url);
if (matches === null) return false;
+ if (matches.groups) return matches.groups;
while (i < result.keys.length) {
out[ result.keys[i] ] = matches[++i] || null;
}
@@ -568,3 +571,214 @@ test('(extra) exec :: loose', t => {
t.end();
});
+
+// ---
+
+test('(RegExp) static', t => {
+ let rgx = /^\/?books/;
+ let { keys, pattern } = fn(rgx);
+ t.same(keys, false, '~> keys = false');
+ t.same(rgx, pattern, '~> pattern = input');
+ t.true(pattern.test('/books'), '~> matches route');
+ t.true(pattern.test('/books/'), '~> matches trailing slash');
+ t.true(pattern.test('/books/'), '~> matches without leading slash');
+ t.end();
+});
+
+if (hasNamedGroups) {
+ test('(RegExp) param', t => {
+ let rgx = /^\/(?[0-9]{4})/i;
+ let { keys, pattern } = fn(rgx);
+ t.same(keys, false, '~> keys = false');
+ t.same(rgx, pattern, '~> pattern = input');
+
+ // RegExp testing (not regexparam related)
+ t.false(pattern.test('/123'), '~> does not match 3-digit string');
+ t.false(pattern.test('/asdf'), '~> does not match 4 alpha characters');
+ t.true(pattern.test('/2019'), '~> matches definition');
+ t.true(pattern.test('/2019/'), '~> matches definition w/ trailing slash');
+ t.false(pattern.test('2019'), '~> does not match without lead slash');
+ t.true(pattern.test('/2019/narnia/hello'), '~> allows extra bits');
+
+ // exec results, array access
+ let [url, value] = pattern.exec('/2019/books');
+ t.is(url, '/2019', '~> executing pattern on correct trimming');
+ t.is(value, '2019', '~> executing pattern gives correct value');
+
+ // exec results, named object
+ t.toExec(rgx, '/2019/books', { year: '2019' });
+ t.toExec(rgx, '/2019/books/narnia', { year: '2019' });
+
+ t.end();
+ });
+
+ test('(RegExp) param :: w/ static', t => {
+ let rgx = /^\/books\/(?[a-z]+)/i;
+ let { keys, pattern } = fn(rgx);
+ t.same(keys, false, '~> keys = false');
+ t.same(rgx, pattern, '~> pattern = input');
+
+ // RegExp testing (not regexparam related)
+ t.false(pattern.test('/books'), '~> does not match naked base');
+ t.false(pattern.test('/books/'), '~> does not match naked base w/ trailing slash');
+ t.true(pattern.test('/books/narnia'), '~> matches definition');
+ t.true(pattern.test('/books/narnia/'), '~> matches definition w/ trailing slash');
+ t.true(pattern.test('/books/narnia/hello'), '~> allows extra bits');
+ t.false(pattern.test('books/narnia'), '~> does not match path without lead slash');
+
+ // exec results, array access
+ let [url, value] = pattern.exec('/books/narnia');
+ t.is(url, '/books/narnia', '~> executing pattern on correct trimming');
+ t.is(value, 'narnia', '~> executing pattern gives correct value');
+
+ // exec results, named object
+ t.toExec(rgx, '/books/narnia', { title: 'narnia' });
+ t.toExec(rgx, '/books/narnia/hello', { title: 'narnia' });
+
+ t.end();
+ });
+
+ test('(RegExp) param :: multiple', t => {
+ let rgx = /^\/(?[0-9]{4})-(?[0-9]{2})\/(?[0-9]{2})/i;
+ let { keys, pattern } = fn(rgx);
+ t.same(keys, false, '~> keys = false');
+ t.same(rgx, pattern, '~> pattern = input');
+
+ // RegExp testing (not regexparam related)
+ t.false(pattern.test('/123-1'));
+ t.false(pattern.test('/123-10'));
+ t.false(pattern.test('/1234-10'));
+ t.false(pattern.test('/1234-10/1'));
+ t.false(pattern.test('/1234-10/as'));
+ t.true(pattern.test('/1234-10/01/'));
+ t.true(pattern.test('/2019-10/30'));
+
+ // exec results, array access
+ let [url, year, month, day] = pattern.exec('/2019-05/30/');
+ t.is(url, '/2019-05/30', '~> executing pattern on correct trimming');
+ t.is(year, '2019', '~> executing pattern gives correct "year" value');
+ t.is(month, '05', '~> executing pattern gives correct "month" value');
+ t.is(day, '30', '~> executing pattern gives correct "day" value');
+
+ // exec results, named object
+ t.toExec(rgx, '/2019-10/02', { year:'2019', month:'10', day:'02' });
+ t.toExec(rgx, '/2019-10/02/narnia', { year:'2019', month:'10', day:'02' });
+
+ t.end();
+ });
+
+ test('(RegExp) param :: suffix', t => {
+ let rgx = /^\/movies[/](?\w+)\.mp4/i;
+ let { keys, pattern } = fn(rgx);
+ t.same(keys, false, '~> keys = false');
+ t.same(rgx, pattern, '~> pattern = input');
+
+ // RegExp testing (not regexparam related)
+ t.false(pattern.test('/movies'));
+ t.false(pattern.test('/movies/'));
+ t.false(pattern.test('/movies/foo'));
+ t.false(pattern.test('/movies/foo.mp3'));
+ t.true(pattern.test('/movies/foo.mp4'));
+ t.true(pattern.test('/movies/foo.mp4/'));
+
+ // exec results, array access
+ let [url, title] = pattern.exec('/movies/narnia.mp4');
+ t.is(url, '/movies/narnia.mp4', '~> executing pattern on correct trimming');
+ t.is(title, 'narnia', '~> executing pattern gives correct "title" value');
+
+ // exec results, named object
+ t.toExec(rgx, '/movies/narnia.mp4', { title: 'narnia' });
+ t.toExec(rgx, '/movies/narnia.mp4/', { title: 'narnia' });
+
+ t.end();
+ });
+
+ test('(RegExp) param :: suffices', t => {
+ let rgx = /^\/movies[/](?\w+)\.(mp4|mov)/i;
+ let { keys, pattern } = fn(rgx);
+ t.same(keys, false, '~> keys = false');
+ t.same(rgx, pattern, '~> pattern = input');
+
+ // RegExp testing (not regexparam related)
+ t.false(pattern.test('/movies'));
+ t.false(pattern.test('/movies/'));
+ t.false(pattern.test('/movies/foo'));
+ t.false(pattern.test('/movies/foo.mp3'));
+ t.true(pattern.test('/movies/foo.mp4'));
+ t.true(pattern.test('/movies/foo.mp4/'));
+ t.true(pattern.test('/movies/foo.mov/'));
+
+ // exec results, array access
+ let [url, title] = pattern.exec('/movies/narnia.mov');
+ t.is(url, '/movies/narnia.mov', '~> executing pattern on correct trimming');
+ t.is(title, 'narnia', '~> executing pattern gives correct "title" value');
+
+ // exec results, named object
+ t.toExec(rgx, '/movies/narnia.mov', { title: 'narnia' });
+ t.toExec(rgx, '/movies/narnia.mov/', { title: 'narnia' });
+
+ t.end();
+ });
+
+ test('(RegExp) param :: optional', t => {
+ let rgx = /^\/books[/](?[^/]+)[/]?(?[^/]+)?[/]?$/
+ let { keys, pattern } = fn(rgx);
+ t.same(keys, false, '~> keys = false');
+ t.same(rgx, pattern, '~> pattern = input');
+
+ // RegExp testing (not regexparam related)
+ t.false(pattern.test('/books'));
+ t.false(pattern.test('/books/'));
+ t.true(pattern.test('/books/smith'));
+ t.true(pattern.test('/books/smith/'));
+ t.true(pattern.test('/books/smith/narnia'));
+ t.true(pattern.test('/books/smith/narnia/'));
+ t.false(pattern.test('/books/smith/narnia/reviews'));
+ t.false(pattern.test('books/smith/narnia'));
+
+ // exec results, array access
+ let [url, author, title] = pattern.exec('/books/smith/narnia/');
+ t.is(url, '/books/smith/narnia/', '~> executing pattern on correct trimming');
+ t.is(author, 'smith', '~> executing pattern gives correct value');
+ t.is(title, 'narnia', '~> executing pattern gives correct value');
+
+ // exec results, named object
+ t.toExec(rgx, '/books/smith/narnia', { author: 'smith', title: 'narnia' });
+ t.toExec(rgx, '/books/smith/narnia/', { author: 'smith', title: 'narnia' });
+ t.toExec(rgx, '/books/smith/', { author: 'smith', title: undefined });
+
+ t.end();
+ });
+}
+
+test('(RegExp) nameless', t => {
+ // For whatever reason~
+ // ~> regexparam CANNOT give `keys` list cuz unknown
+ let rgx = /^\/books[/]([^/]\w+)[/]?(\w+)?(?=\/|$)/i;
+ let { keys, pattern } = fn(rgx);
+ t.same(keys, false, '~> keys = false');
+ t.same(rgx, pattern, '~> pattern = input');
+
+ // RegExp testing (not regexparam related)
+ t.false(pattern.test('/books'));
+ t.false(pattern.test('/books/'));
+ t.true(pattern.test('/books/smith'));
+ t.true(pattern.test('/books/smith/'));
+ t.true(pattern.test('/books/smith/narnia'));
+ t.true(pattern.test('/books/smith/narnia/'));
+ t.false(pattern.test('books/smith/narnia'));
+
+ // exec results, array access
+ let [url, author, title] = pattern.exec('/books/smith/narnia/');
+ t.is(url, '/books/smith/narnia', '~> executing pattern on correct trimming');
+ t.is(author, 'smith', '~> executing pattern gives correct value');
+ t.is(title, 'narnia', '~> executing pattern gives correct value');
+
+ // exec results, named object
+ // Note: UNKNOWN & UNNAMED KEYS
+ t.toExec(rgx, '/books/smith/narnia', {});
+ t.toExec(rgx, '/books/smith/narnia/', {});
+ t.toExec(rgx, '/books/smith/', {});
+
+ t.end();
+});
diff --git a/types.d.ts b/types.d.ts
index 167bd15..5aa4f10 100644
--- a/types.d.ts
+++ b/types.d.ts
@@ -1,6 +1,11 @@
-export interface RouteParsed {
+declare function regexparam(route: string, loose?: boolean): {
keys: Array,
pattern: RegExp
}
-declare const regexparam: (route: string, loose?: boolean) => RouteParsed;
+
+declare function regexparam(route: RegExp): {
+ keys: false,
+ pattern: RegExp
+}
+
export default regexparam;