1
+ const fs = require ( 'fs' ) ;
2
+ const url = require ( 'url' ) ;
3
+
4
+ function removeQuotes ( str ) {
5
+ return str . trim ( ) . replace ( / " | ' / ig, '' ) ;
6
+ } ;
7
+
8
+ const parseLine = line => {
9
+ if ( ! line . trim ( ) || / ^ ( # | \/ \/ ) / . test ( line ) ) return ;
10
+ const m = / ( \w + ) \s + ( .+ ) \s * = > \s * ( .+ ) # ( \w + ) ( \s * , ( .+ ) ) ? / . exec ( line ) ;
11
+ if ( ! m ) throw new SyntaxError ( 'Unexpected token' , line ) ;
12
+ let domain , path = removeQuotes ( m [ 2 ] . trim ( ) ) ;
13
+ const i = path . indexOf ( '/' ) ;
14
+ if ( i > 0 ) {
15
+ domain = path . substr ( 0 , i ) ;
16
+ path = path . substr ( i ) ;
17
+ }
18
+ return {
19
+ domain,
20
+ path,
21
+ action : removeQuotes ( m [ 4 ] . trim ( ) ) ,
22
+ controller : removeQuotes ( m [ 3 ] . trim ( ) ) ,
23
+ options : m [ 6 ] && JSON . parse ( m [ 6 ] ) ,
24
+ method : m [ 1 ] . trim ( ) . toUpperCase ( ) ,
25
+ }
26
+ } ;
27
+
28
+ const parse = content =>
29
+ content . split ( / \r ? \n / g)
30
+ . map ( parseLine )
31
+ . filter ( Boolean )
32
+ . map ( create ) ;
33
+
34
+ const load = filename =>
35
+ parse ( fs . readFileSync ( filename , 'utf8' ) ) ;
36
+
37
+ const pathToRegexp = path => {
38
+ if ( path instanceof RegExp ) return path ;
39
+ let arr = path . split ( '/' ) , pattern = '' , keys = [ ] ;
40
+ ( arr [ 0 ] === '' ) && arr . shift ( ) ;
41
+ arr . forEach ( ( p , i ) => {
42
+ switch ( p [ 0 ] ) {
43
+ case '*' :
44
+ pattern += '/(.+)' ;
45
+ keys . push ( p . substring ( 1 ) || `$${ i } ` ) ;
46
+ break ;
47
+ case ':' :
48
+ const o = p . substr ( - 1 ) ;
49
+ const r = '([^/]+?)' ;
50
+ const m = {
51
+ '?' : `(?:/${ r } )?` ,
52
+ '*' : '(?:/)(.*)'
53
+ } ;
54
+ pattern += m [ o ] || `/${ r } ` ;
55
+ keys . push ( p . substring ( 1 , p . length - ! ! m [ o ] ) ) ;
56
+ break ;
57
+ default :
58
+ pattern += `/${ p } ` ;
59
+ break ;
60
+ }
61
+ } ) ;
62
+ keys . length && ( pattern += '(?:/)?' ) ;
63
+ pattern = new RegExp ( `^${ pattern } \/?$` , 'i' ) ;
64
+ pattern . keys = keys ;
65
+ pattern . parse = function ( pathname ) {
66
+ if ( this . test ( pathname ) === false ) return null ;
67
+ return this . exec ( pathname ) . slice ( 1 ) . reduce ( ( params , param , i ) => {
68
+ params [ this . keys [ i ] ] = param && decodeURIComponent ( param ) ;
69
+ return params ;
70
+ } , { } ) ;
71
+ } ;
72
+ return pattern ;
73
+ } ;
74
+
75
+ const find = ( routes , req ) => {
76
+ const [ domain ] = ( req . host || '' ) . split ( ':' ) ; // omit port
77
+ const { pathname, query } = url . parse ( req . url , true ) ;
78
+ const m = routes
79
+ . filter ( route =>
80
+ ( route . domain ? route . domain === domain : true ) &&
81
+ route . regexp . test ( pathname ) )
82
+ . sort ( ( a , b ) => {
83
+ const { priority : aPriority = 0 } = a ;
84
+ const { priority : bPriority = 0 } = b ;
85
+ return bPriority - aPriority ;
86
+ } ) ;
87
+ if ( ! m . length ) return { status : 404 } ;
88
+ const allowMethods = m . map ( route => route . method . toUpperCase ( ) ) ;
89
+ const methodIndex = allowMethods . findIndex ( x => x === '*' || x === req . method . toUpperCase ( ) ) ;
90
+ if ( ! ~ methodIndex && req . method === 'OPTIONS' )
91
+ return { status : 204 , allowMethods, routes : m } ;
92
+ if ( ! ~ methodIndex ) return { status : 405 , allowMethods, routes : m } ;
93
+ const route = m [ methodIndex ] ;
94
+ const params = route . regexp . parse ( pathname ) ;
95
+ // will deprecated in future
96
+ route . params = params ;
97
+ return { status : 200 , route, params, query } ;
98
+ } ;
99
+
100
+ const create = route => {
101
+ if ( typeof route === 'string' )
102
+ route = parseLine ( route ) ;
103
+ route . method = ( route . method || '*' ) ;
104
+ route . regexp = pathToRegexp ( route . path ) ;
105
+ return route ;
106
+ } ;
107
+
108
+ module . exports = {
109
+ find,
110
+ load,
111
+ parse,
112
+ create,
113
+ parseLine,
114
+ pathToRegexp
115
+ } ;
0 commit comments