From: Eduardo San Martin Morote Date: Tue, 10 Dec 2019 23:03:02 +0000 (+0100) Subject: feat: repeatable params X-Git-Tag: v4.0.0-alpha.0~156 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=1171fb2f55a47e88e9217bea216e302346390996;p=thirdparty%2Fvuejs%2Frouter.git feat: repeatable params --- diff --git a/__tests__/matcher/path-parser.spec.ts b/__tests__/matcher/path-parser.spec.ts index 2145eb67..1576ce3d 100644 --- a/__tests__/matcher/path-parser.spec.ts +++ b/__tests__/matcher/path-parser.spec.ts @@ -1,7 +1,7 @@ import { tokenizePath, TokenType, - tokensToRegExp, + tokensToParser, } from '../../src/matcher/tokenizer' describe('Path parser', () => { @@ -196,12 +196,12 @@ describe('Path parser', () => { }) }) - describe('tokensToRegexp', () => { + describe('tokensToParser', () => { function matchRegExp( expectedRe: string, - ...args: Parameters + ...args: Parameters ) { - const pathParser = tokensToRegExp(...args) + const pathParser = tokensToParser(...args) expect(expectedRe).toBe( pathParser.re .toString() @@ -302,4 +302,62 @@ describe('Path parser', () => { }) // end of describe }) + + describe('parsing urls', () => { + function matchParams( + path: string, + pathToTest: string, + params: ReturnType['parse']> + ) { + const pathParser = tokensToParser(tokenizePath(path)) + + expect(pathParser.parse(pathToTest)).toEqual(params) + } + + it('returns null if no match', () => { + matchParams('/home', '/', null) + }) + + it('returns an empty object with no keys', () => { + matchParams('/home', '/home', {}) + }) + + it('param single', () => { + matchParams('/:id', '/a', { id: 'a' }) + }) + + it('param combined', () => { + matchParams('/hey:a', '/heyedu', { + a: 'edu', + }) + }) + + it('param multiple', () => { + matchParams('/:a-:b-:c', '/one-two-three', { + a: 'one', + b: 'two', + c: 'three', + }) + }) + + it('param optional', () => { + matchParams('/:a?', '/', { + a: '', + }) + matchParams('/:a*', '/', { + a: '', + }) + }) + + it('param repeatable', () => { + matchParams('/:a+', '/one/two', { + a: ['one', 'two'], + }) + matchParams('/:a*', '/one/two', { + a: ['one', 'two'], + }) + }) + + // end of parsing urls + }) }) diff --git a/src/matcher/tokenizer.ts b/src/matcher/tokenizer.ts index dcad9167..dee2dd93 100644 --- a/src/matcher/tokenizer.ts +++ b/src/matcher/tokenizer.ts @@ -74,6 +74,10 @@ export function tokenizePath(path: string): Array { state === TokenizerState.Param || state === TokenizerState.ParamRegExp ) { + if (segment.length > 1 && (char === '*' || char === '+')) + crash( + `A repeatable param (${buffer}) must be alone in its segment. eg: '/:ids+.` + ) segment.push({ type: TokenType.Param, value: buffer, @@ -132,7 +136,9 @@ export function tokenizePath(path: string): Array { consumeBuffer() state = TokenizerState.Static // go back one character if we were not modifying - if (char !== '*' && char !== '?' && char !== '+') i-- + if (char !== '*' && char !== '?' && char !== '+') { + i-- + } } break @@ -160,18 +166,28 @@ export function tokenizePath(path: string): Array { return tokens } +type Params = Record + +interface ParamKey { + name: string + repeatable: boolean + optional: boolean +} + interface PathParser { re: RegExp score: number - keys: string[] + keys: ParamKey[] + parse(path: string): Params | null + stringify(params: Params): string } const BASE_PARAM_PATTERN = '[^/]+?' -export function tokensToRegExp(segments: Array): PathParser { +export function tokensToParser(segments: Array): PathParser { let score = 0 let pattern = '^' - const keys: string[] = [] + const keys: ParamKey[] = [] for (const segment of segments) { pattern += '/' @@ -180,7 +196,11 @@ export function tokensToRegExp(segments: Array): PathParser { if (token.type === TokenType.Static) { pattern += token.value } else if (token.type === TokenType.Param) { - keys.push(token.value) + keys.push({ + name: token.value, + repeatable: token.repeatable, + optional: token.optional, + }) const re = token.regexp ? token.regexp : BASE_PARAM_PATTERN pattern += token.repeatable ? `((?:${re})(?:/(?:${re}))*)` : `(${re})` if (token.optional) pattern += '?' @@ -190,9 +210,35 @@ export function tokensToRegExp(segments: Array): PathParser { pattern += '$' + const re = new RegExp(pattern) + + function parse(path: string): Params | null { + const match = path.match(re) + const params: Params = {} + + if (!match) return null + + for (let i = 1; i < match.length; i++) { + const value: string = match[i] || '' + const key = keys[i - 1] + params[key.name] = value && key.repeatable ? value.split('/') : value + } + + return params + } + + function stringify(params: Params): string { + let path = '' + // TODO: implem + + return path + } + return { - re: new RegExp(pattern), + re, score, keys, + parse, + stringify, } }