@@ -11,41 +11,47 @@ export function hasher<S extends string>(spec: S): Hasher<[ParamsOf<S>]> {
1111 if ( ! spec . startsWith ( "#/" ) )
1212 throw new Error ( `hash route spec must start with "#/"` )
1313
14- // capture names in order
15- const names : string [ ] = [ ]
16- spec . replace ( / \{ ( [ ^ } ] + ) \} / g, ( _m , name : string ) => {
17- if ( names . includes ( name ) ) throw new Error ( `route hasher spec "${ spec } " has forbidden duplicate name "${ name } "` )
18- names . push ( name )
19- return ""
20- } )
21-
22- // build a regex that matches the full hash
23- const escape = ( s : string ) => s . replace ( / [ . * + ? ^ $ { } ( ) | [ \] \\ ] / g, "\\$&" )
24- const pattern = "^" + escape ( spec ) . replace ( / \\ \{ [ ^ } ] + \\ \} / g, "([^/]+)" ) + "$"
25- const regex = new RegExp ( pattern )
14+ const specparts = spec . split ( "/" )
15+ const braceregex = / \{ ( [ ^ \} \/ ] + ) \} /
2616
2717 function parse ( hash : string ) : [ ParamsOf < S > ] | null {
28- const m = regex . exec ( hash )
29- if ( ! m ) return null
30- const obj : Record < string , string > = { }
31- for ( const [ index , name ] of names . entries ( ) ) {
32- try { obj [ name ] = decodeURIComponent ( m [ index + 1 ] ) }
33- catch { return null }
18+ if ( ! hash . startsWith ( "#/" ) )
19+ throw new Error ( `hash must start with "#/"` )
20+
21+ const hashparts = hash . split ( "/" )
22+ const params : Record < string , string > = { }
23+
24+ if ( hashparts . length !== specparts . length )
25+ return null
26+
27+ for ( const [ index , specpart ] of specparts . entries ( ) ) {
28+ const hashpart = hashparts . at ( index )
29+ if ( hashpart === undefined ) return null
30+ const bracematch = specpart . match ( braceregex )
31+ try {
32+ if ( bracematch ) params [ bracematch [ 1 ] ] = decodeURIComponent ( hashpart )
33+ else if ( hashpart !== specpart ) return null
34+ }
35+ catch {
36+ return null
37+ }
3438 }
35- return [ obj as ParamsOf < S > ]
39+
40+ return [ params as ParamsOf < S > ]
3641 }
3742
38- function make ( ...[ obj ] : [ ParamsOf < S > ] ) : string {
39- let out = spec
40- for ( const name of names ) {
41- const v = ( obj as Record < string , string > ) [ name ]
42- if ( v == null ) throw new Error ( `missing param "${ name } "` )
43- out = out . replace (
44- new RegExp ( `\\{${ name . replace ( / [ . * + ? ^ $ { } ( ) | [ \\ ] \\ \\ ] / g, "\\$&" ) } \\}` ) ,
45- encodeURIComponent ( v ) ,
46- ) as any
43+ function make ( params : ParamsOf < S > ) : string {
44+ const get = ( param : string ) => {
45+ const p = param as keyof typeof params
46+ if ( p in params ) return params [ p ]
47+ else throw new Error ( `missing param "${ p } "` )
4748 }
48- return out
49+ return specparts . map ( specpart => {
50+ const bracematch = specpart . match ( braceregex )
51+ return bracematch
52+ ? encodeURIComponent ( get ( bracematch [ 1 ] ) )
53+ : specpart
54+ } ) . join ( "/" )
4955 }
5056
5157 return { parse, make}
0 commit comments