/**
 * TS adaption of https://github.com/pillarjs/path-to-regexp/blob/master/index.js
 */ export interface RegExpOptions { sensitive ? : boolean ; strict ? : boolean ; end ? : boolean ; delimiter ? : string ; delimiters ? : string | string [ ] ; endsWith ? : string | string [ ] ; } export interface ParseOptions { delimiter ? : string ; delimiters ? : string | string [ ] ; } export interface Key { name : string | number ; prefix : string | null ; delimiter : string | null ; optional : boolean ; repeat : boolean ; pattern : string | null ; partial : boolean ; } export interface PathFunctionOptions { encode ? : ( value : string ) => string ; } export type Token = string | Key ; export type Path = string | RegExp | Array < string | RegExp > ; export type PathFunction = ( data ? : { [ key ] : any ; } , options ? : PathFunctionOptions ) => string ; /**
 * Default configs.
 */ const DEFAULT_DELIMITER = '/' ; const DEFAULT_DELIMITERS = './' ; /**
 * The main path matching regexp utility.
 */ const PATH_REGEXP = new RegExp ( [ '(\\\\.)' , '(?:\\:(\\w+)(?:\\(((?:\\\\.|[^\\\\()])+)\\))?|\\(((?:\\\\.|[^\\\\()])+)\\))([+*?])?' , ] . join ( '|' ) , 'g' ) ; /**
 * Parse a string for the raw tokens.
 */ export const parse = ( str , options ? ) : Token [ ] => { var tokens = [ ] ; var key = 0 ; var index = 0 ; var path = '' ; var defaultDelimiter = ( options && options . delimiter ) || DEFAULT_DELIMITER ; var delimiters = ( options && options . delimiters ) || DEFAULT_DELIMITERS ; var pathEscaped = false ; var res ; while ( ( res = PATH_REGEXP . exec ( str ) ) !== null ) { var m = res [ 0 ] ; var escaped = res [ 1 ] ; var offset = res . index ; path += str . slice ( index , offset ) ; index = offset + m . length ; if ( escaped ) { path += escaped [ 1 ] ; pathEscaped = true ; continue ; } var prev = '' ; var next = str [ index ] ; var name = res [ 2 ] ; var capture = res [ 3 ] ; var group = res [ 4 ] ; var modifier = res [ 5 ] ; if ( ! pathEscaped && path . length ) { var k = path . length - 1 ; if ( delimiters . indexOf ( path [ k ] ) > - 1 ) { prev = path [ k ] ; path = path . slice ( 0 , k ) ; } } if ( path ) { tokens . push ( path ) ; path = '' ; pathEscaped = false ; } var partial = prev !== '' && next !== undefined && next !== prev ; var repeat = modifier === '+' || modifier === '*' ; var optional = modifier === '?' || modifier === '*' ; var delimiter = prev || defaultDelimiter ; var pattern = capture || group ; tokens . push ( { name : name || key ++ , prefix : prev , delimiter : delimiter , optional : optional , repeat : repeat , partial : partial , pattern : pattern ? escapeGroup ( pattern ) : '[^' + escapeString ( delimiter ) + ']+?' , } ) ; } if ( path || index < str . length ) { tokens . push ( path + str . substr ( index ) ) ; } return tokens ; } ; /**
 * Compile a string to a template function for the path.
 */ export const compile = ( str , options ? ) => { return tokensToFunction ( parse ( str , options ) ) ; } ; /**
 * Expose a method for transforming tokens into the path function.
 */ export const tokensToFunction = ( tokens ) : PathFunction => { var matches = new Array ( tokens . length ) ; for ( var i = 0 ; i < tokens . length ; i ++ ) { var token = tokens [ i ] ; if ( typeof token === 'object' ) { matches [ i ] = new RegExp ( '^(?:' + token . pattern + ')$' ) ; } } return ( data ? , options ? ) : string => { var path = '' ; var encode = ( options && options . encode ) || encodeURIComponent ; for ( var i = 0 ; i < tokens . length ; i ++ ) { var token = tokens [ i ] ; if ( typeof token === 'string' ) { path += token ; continue ; } var value = data ? data [ token . name ] : undefined ; var segment ; if ( Array . isArray ( value ) ) { if ( ! token . repeat ) { throw new TypeError ( 'Expected "' + token . name + '" to not repeat, but got array' ) ; } if ( value . length === 0 ) { if ( token . optional ) continue ; throw new TypeError ( 'Expected "' + token . name + '" to not be empty' ) ; } for ( var j = 0 ; j < value . length ; j ++ ) { segment = encode ( value [ j ] ) ; if ( ! matches [ i ] . test ( segment ) ) { throw new TypeError ( 'Expected all "' + token . name + '" to match "' + token . pattern + '"' ) ; } path += ( j === 0 ? token . prefix : token . delimiter ) + segment ; } continue ; } if ( typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean' ) { segment = encode ( String ( value ) ) ; if ( ! matches [ i ] . test ( segment ) ) { throw new TypeError ( 'Expected "' + token . name + '" to match "' + token . pattern + '", but got "' + segment + '"' ) ; } path += token . prefix + segment ; continue ; } if ( token . optional ) { if ( token . partial ) path += token . prefix ; continue ; } throw new TypeError ( 'Expected "' + token . name + '" to be ' + ( token . repeat ? 'an array' : 'a string' ) ) ; } return path ; } ; } ; /**
 * Escape a regular expression string.
 */ const escapeString = ( str ) => { return str . replace ( /([.+*?=^!:${}()[\]|/\\])/g , '\\$1' ) ; } ; /**
 * Escape the capturing group by escaping special characters and meaning.
 */ const escapeGroup = ( group ) => { return group . replace ( /([=!:$/()])/g , '\\$1' ) ; } ; /**
 * Get the flags for a regexp from the options.
 */ const flags = ( options ) : string => { return options && options . sensitive ? '' : 'i' ; } ; /**
 * Pull out keys from a regexp.
 */ const regexpToRegexp = ( path , keys ) : RegExp => { if ( ! keys ) return path ; var groups = path . source . match ( /\((?!\?)/g ) ; if ( groups ) { for ( var i = 0 ; i < groups . length ; i ++ ) { keys . push ( { name : i , prefix : null , delimiter : null , optional : false , repeat : false , partial : false , pattern : null , } ) ; } } return path ; } ; /**
 * Transform an array into a regexp.
 */ const arrayToRegexp = ( path , keys , options ) : RegExp => { var parts = [ ] ; for ( var i = 0 ; i < path . length ; i ++ ) { parts . push ( pathToRegexp ( path [ i ] , keys , options ) . source ) ; } return new RegExp ( '(?:' + parts . join ( '|' ) + ')' , flags ( options ) ) ; } ; /**
 * Create a path regexp from string input.
 */ const stringToRegexp = ( path , keys , options ) : RegExp => { return tokensToRegExp ( parse ( path , options ) , keys , options ) ; } ; /**
 * Expose a function for taking tokens and returning a RegExp.
 */ export const tokensToRegExp = ( tokens , keys ? , options ? ) : RegExp => { options = options || { } ; var strict = options . strict ; var end = options . end !== false ; var delimiter = escapeString ( options . delimiter || DEFAULT_DELIMITER ) ; var delimiters = options . delimiters || DEFAULT_DELIMITERS ; var endsWith = ( < Array < string > > [ ] ) . concat ( options . endsWith || [ ] ) . map ( escapeString ) . concat ( '$' ) . join ( '|' ) ; var route = '' ; var isEndDelimited = false ; for ( var i = 0 ; i < tokens . length ; i ++ ) { var token = tokens [ i ] ; if ( typeof token === 'string' ) { route += escapeString ( token ) ; isEndDelimited = i === tokens . length - 1 && delimiters . indexOf ( token [ token . length - 1 ] ) > - 1 ; } else { var prefix = escapeString ( token . prefix || '' ) ; var capture = token . repeat ? '(?:' + token . pattern + ')(?:' + prefix + '(?:' + token . pattern + '))*' : token . pattern ; if ( keys ) keys . push ( token ) ; if ( token . optional ) { if ( token . partial ) { route += prefix + '(' + capture + ')?' ; } else { route += '(?:' + prefix + '(' + capture + '))?' ; } } else { route += prefix + '(' + capture + ')' ; } } } if ( end ) { if ( ! strict ) route += '(?:' + delimiter + ')?' ; route += endsWith === '$' ? '$' : '(?=' + endsWith + ')' ; } else { if ( ! strict ) route += '(?:' + delimiter + '(?=' + endsWith + '))?' ; if ( ! isEndDelimited ) route += '(?=' + delimiter + '|' + endsWith + ')' ; } return new RegExp ( '^' + route , flags ( options ) ) ; } ; /**
 * Normalize the given path string, returning a regular expression.
 *
 * An empty array can be passed in for the keys, which will hold the
 * placeholder key descriptions. For example, using `/user/:id`, `keys` will
 * contain `[{ name: 'id', delimiter: '/', optional: false, repeat: false }]`.
 */ export const pathToRegexp = ( path , keys , options ) : RegExp => { if ( path instanceof RegExp ) { return regexpToRegexp ( path , keys ) ; } if ( Array . isArray ( path ) ) { return arrayToRegexp ( path , keys , options ) ; } return stringToRegexp ( path , keys , options ) ; } ;