export type Pattern = readonly [string, string, RegExp | true] | '*' export const splitPath = (path: string): string[] => { const paths = path.split(/\//) // faster than path.split('/') if (paths[0] === '') { paths.shift() } return paths } export const splitRoutingPath = (path: string): string[] => { const groups: [string, string][] = [] // [mark, original string] for (let i = 0; ; ) { let replaced = false path = path.replace(/\{[^}]+\}/g, (m) => { const mark = `@\\${i}` groups[i] = [mark, m] i++ replaced = true return mark }) if (!replaced) { break } } const paths = path.split(/\//) // faster than path.split('/') if (paths[0] === '') { paths.shift() } for (let i = groups.length - 1; i >= 0; i--) { const [mark] = groups[i] for (let j = paths.length - 1; j >= 0; j--) { if (paths[j].indexOf(mark) !== -1) { paths[j] = paths[j].replace(mark, groups[i][1]) break } } } return paths } const patternCache: { [key: string]: Pattern } = {} export const getPattern = (label: string): Pattern | null => { // * => wildcard // :id{[0-9]+} => ([0-9]+) // :id => (.+) //const name = '' if (label === '*') { return '*' } const match = label.match(/^\:([^\{\}]+)(?:\{(.+)\})?$/) if (match) { if (!patternCache[label]) { if (match[2]) { patternCache[label] = [label, match[1], new RegExp('^' + match[2] + '$')] } else { patternCache[label] = [label, match[1], true] } } return patternCache[label] } return null } export const getPathFromURL = (url: string, strict: boolean = true): [string, number] => { const queryIndex = url.indexOf('?') const result = url.substring(url.indexOf('/', 8), queryIndex === -1 ? url.length : queryIndex) // if strict routing is false => `/hello/hey/` and `/hello/hey` are treated the same // default is true if (strict === false && result.endsWith('/')) { return [result.slice(0, -1), queryIndex] } return [result, queryIndex] } export const getQueryStringFromURL = (url: string, queryIndex?: number): string => { queryIndex ||= url.indexOf('?') const result = queryIndex !== -1 ? url.substring(queryIndex) : '' return result } export const mergePath = (...paths: string[]): string => { let p: string = '' let endsWithSlash = false for (let path of paths) { /* ['/hey/','/say'] => ['/hey', '/say'] */ if (p.endsWith('/')) { p = p.slice(0, -1) endsWithSlash = true } /* ['/hey','say'] => ['/hey', '/say'] */ if (!path.startsWith('/')) { path = `/${path}` } /* ['/hey/', '/'] => `/hey/` */ if (path === '/' && endsWithSlash) { p = `${p}/` } else if (path !== '/') { p = `${p}${path}` } /* ['/', '/'] => `/` */ if (path === '/' && p === '') { p = '/' } } return p } export const checkOptionalParameter = (path: string): string[] | null => { /* If path is `/api/animals/:type?` it will return: [`/api/animals`, `/api/animals/:type`] in other cases it will return null */ const match = path.match(/(^.+)(\/\:[^\/]+)\?$/) if (!match) return null const base = match[1] const optional = base + match[2] return [base, optional] }