From 64e746fea21893a7ec2dcc075fd28c79e2c0fadb Mon Sep 17 00:00:00 2001 From: Luke Edwards Date: Sun, 7 Jul 2019 16:26:32 -0700 Subject: [PATCH 1/9] feat: allow custom RegExp pattern --- src/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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()) { From 3265e0cfc2a1d766a36886a9847bb2f422c6e9e3 Mon Sep 17 00:00:00 2001 From: Luke Edwards Date: Sun, 7 Jul 2019 16:26:53 -0700 Subject: [PATCH 2/9] chore: add RegExp pattern tests --- test/index.js | 227 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 227 insertions(+) diff --git a/test/index.js b/test/index.js index 60d2ce6..6002dc8 100644 --- a/test/index.js +++ b/test/index.js @@ -5,6 +5,7 @@ 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 +569,229 @@ 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(); +}); + +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 = /^\/(?<year>[0-9]{4})-(?<month>[0-9]{2})\/(?<day>[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[/](?<title>\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[/](?<title>\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[/](?<author>[^/]+)[/]?(?<title>[^/]+)?[/]?$/ + 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('param :: optional', t => { + let { keys, pattern } = fn('/books/:author/:title?'); + t.same(keys, ['author', 'title'], '~> keys has "author" & "title" values'); + 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/smith'), '~> matches when optional parameter is missing counts'); + t.true(pattern.test('/books/smith/'), '~> matches when optional paramter is missing w/ trailing slash'); + t.true(pattern.test('/books/smith/narnia'), '~> matches when fully populated'); + t.true(pattern.test('/books/smith/narnia/'), '~> matches when fully populated w/ trailing slash'); + t.false(pattern.test('/books/smith/narnia/reviews'), '~> does not match extra bits'); + t.false(pattern.test('books/smith/narnia'), '~> does not match path without lead slash'); + let [_, author, title] = pattern.exec('/books/smith/narnia'); + t.is(author, 'smith', '~> executing pattern gives correct value'); + t.is(title, 'narnia', '~> executing pattern gives correct value'); + 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(); +}); From 904a2fe2eb9cad05072f7af283d7b1380496afa5 Mon Sep 17 00:00:00 2001 From: Luke Edwards <luke.edwards05@gmail.com> Date: Mon, 8 Jul 2019 18:13:08 -0700 Subject: [PATCH 3/9] chore: update types --- types.d.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) 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<string>, pattern: RegExp } -declare const regexparam: (route: string, loose?: boolean) => RouteParsed; + +declare function regexparam(route: RegExp): { + keys: false, + pattern: RegExp +} + export default regexparam; From 4c7a960426acac66e24d8020a0f6a8a7aadd93a4 Mon Sep 17 00:00:00 2001 From: Luke Edwards <luke.edwards05@gmail.com> Date: Mon, 8 Jul 2019 18:47:03 -0700 Subject: [PATCH 4/9] chore: update readme docs --- readme.md | 48 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 4a0e45e..059718e 100644 --- a/readme.md +++ b/readme.md @@ -87,14 +87,48 @@ 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`~!<br> +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[/](?<year>[0-9]{4})[/](?<month>[0-9]{2})[/](?<title>[^\/]+)/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.<br> +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` +Type: `String` or `RegExp` The route/pathing string to convert. @@ -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 From d588df445739c2dbdfc6d7ee6fc769bd4c85bbf3 Mon Sep 17 00:00:00 2001 From: Luke Edwards <luke.edwards05@gmail.com> Date: Mon, 8 Jul 2019 19:35:45 -0700 Subject: [PATCH 5/9] chore: conditional named RegExp testing --- .travis.yml | 1 + test/index.js | 347 ++++++++++++++++++++++++-------------------------- 2 files changed, 168 insertions(+), 180 deletions(-) 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/test/index.js b/test/index.js index 6002dc8..7e563d0 100644 --- a/test/index.js +++ b/test/index.js @@ -1,6 +1,8 @@ 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); @@ -583,186 +585,171 @@ test('(RegExp) static', t => { t.end(); }); -test('(RegExp) param', t => { - let rgx = /^\/(?<year>[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\/(?<title>[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 = /^\/(?<year>[0-9]{4})-(?<month>[0-9]{2})\/(?<day>[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[/](?<title>\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[/](?<title>\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[/](?<author>[^/]+)[/]?(?<title>[^/]+)?[/]?$/ - 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('param :: optional', t => { - let { keys, pattern } = fn('/books/:author/:title?'); - t.same(keys, ['author', 'title'], '~> keys has "author" & "title" values'); - 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/smith'), '~> matches when optional parameter is missing counts'); - t.true(pattern.test('/books/smith/'), '~> matches when optional paramter is missing w/ trailing slash'); - t.true(pattern.test('/books/smith/narnia'), '~> matches when fully populated'); - t.true(pattern.test('/books/smith/narnia/'), '~> matches when fully populated w/ trailing slash'); - t.false(pattern.test('/books/smith/narnia/reviews'), '~> does not match extra bits'); - t.false(pattern.test('books/smith/narnia'), '~> does not match path without lead slash'); - let [_, author, title] = pattern.exec('/books/smith/narnia'); - t.is(author, 'smith', '~> executing pattern gives correct value'); - t.is(title, 'narnia', '~> executing pattern gives correct value'); - t.end(); -}); +if (hasNamedGroups) { + test('(RegExp) param', t => { + let rgx = /^\/(?<year>[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\/(?<title>[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 = /^\/(?<year>[0-9]{4})-(?<month>[0-9]{2})\/(?<day>[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[/](?<title>\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[/](?<title>\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[/](?<author>[^/]+)[/]?(?<title>[^/]+)?[/]?$/ + 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~ From 76002c462cc0c0e01c935d12c347830b7849346a Mon Sep 17 00:00:00 2001 From: Luke Edwards <luke.edwards05@gmail.com> Date: Mon, 8 Jul 2019 19:41:14 -0700 Subject: [PATCH 6/9] chore: update size --- package.json | 2 +- readme.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 7100c2c..5d800fe 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "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` 🙇‍", "module": "dist/regexparam.mjs", "main": "dist/regexparam.js", "types": "types.d.ts", diff --git a/readme.md b/readme.md index 059718e..4143a92 100644 --- a/readme.md +++ b/readme.md @@ -1,6 +1,6 @@ # regexparam [![Build Status](https://travis-ci.org/lukeed/regexparam.svg?branch=master)](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. From 6c1f4888c49e02e39d62b61e9d6b052060c2e439 Mon Sep 17 00:00:00 2001 From: Luke Edwards <luke.edwards05@gmail.com> Date: Mon, 8 Jul 2019 19:41:27 -0700 Subject: [PATCH 7/9] feat: attach "unpkg" key --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 5d800fe..6a8576e 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "version": "1.2.2", "repository": "lukeed/regexparam", "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", From 9aad3c6b28836aab6c4755344b63ea838338af17 Mon Sep 17 00:00:00 2001 From: Luke Edwards <luke.edwards05@gmail.com> Date: Mon, 8 Jul 2019 19:41:34 -0700 Subject: [PATCH 8/9] chore: update badge --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 4143a92..88bc30c 100644 --- a/readme.md +++ b/readme.md @@ -1,4 +1,4 @@ -# regexparam [![Build Status](https://travis-ci.org/lukeed/regexparam.svg?branch=master)](https://travis-ci.org/lukeed/regexparam) +# regexparam [![Build Status](https://badgen.now.sh/travis/lukeed/regexparam)](https://travis-ci.org/lukeed/regexparam) > A tiny (308B) utility that converts route patterns into RegExp. Limited alternative to [`path-to-regexp`](https://github.com/pillarjs/path-to-regexp) 🙇 From 4e1c43ac6231af8c0fd63adf29ef87b2782d0711 Mon Sep 17 00:00:00 2001 From: Luke Edwards <luke.edwards05@gmail.com> Date: Mon, 8 Jul 2019 19:46:03 -0700 Subject: [PATCH 9/9] chore: fix readme docs --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 88bc30c..91a696e 100644 --- a/readme.md +++ b/readme.md @@ -128,7 +128,7 @@ 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` or `RegExp` +Type: `String` The route/pathing string to convert.