Skip to content
Projects
Groups
Snippets
Help
Loading...
Sign in
Toggle navigation
B
beego
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Commits
Issue Boards
Open sidebar
go
beego
Commits
bfabcfcb
Commit
bfabcfcb
authored
Jun 04, 2014
by
astaxie
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
beego:router tree
parent
f06ba52e
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
424 additions
and
0 deletions
+424
-0
tree.go
tree.go
+306
-0
tree_test.go
tree_test.go
+118
-0
No files found.
tree.go
0 → 100644
View file @
bfabcfcb
package
beego
import
(
"path"
"regexp"
"strings"
"github.com/astaxie/beego/utils"
)
type
Tree
struct
{
//search fix route first
fixrouters
map
[
string
]
*
Tree
//if set, failure to match fixrouters search then search wildcard
wildcard
*
Tree
//if set, failure to match wildcard search
leaf
*
leafInfo
}
func
NewTree
()
*
Tree
{
return
&
Tree
{
fixrouters
:
make
(
map
[
string
]
*
Tree
),
}
}
// call addseg function
func
(
t
*
Tree
)
AddRouter
(
pattern
string
,
runObject
interface
{})
{
t
.
addseg
(
splitPath
(
pattern
),
runObject
,
nil
,
""
)
}
// "/"
// "admin" ->
func
(
t
*
Tree
)
addseg
(
segments
[]
string
,
route
interface
{},
wildcards
[]
string
,
reg
string
)
{
if
len
(
segments
)
==
0
{
if
reg
!=
""
{
filterCards
:=
[]
string
{}
for
_
,
v
:=
range
wildcards
{
if
v
==
":"
||
v
==
"."
{
continue
}
filterCards
=
append
(
filterCards
,
v
)
}
t
.
leaf
=
&
leafInfo
{
runObject
:
route
,
wildcards
:
wildcards
,
regexps
:
regexp
.
MustCompile
(
"^"
+
reg
+
"$"
)}
}
else
{
t
.
leaf
=
&
leafInfo
{
runObject
:
route
,
wildcards
:
wildcards
}
}
}
else
{
seg
:=
segments
[
0
]
iswild
,
params
,
regexpStr
:=
splitSegment
(
seg
)
if
iswild
{
if
t
.
wildcard
==
nil
{
t
.
wildcard
=
NewTree
()
}
t
.
wildcard
.
addseg
(
segments
[
1
:
],
route
,
append
(
wildcards
,
params
...
),
reg
+
regexpStr
)
}
else
{
subTree
,
ok
:=
t
.
fixrouters
[
seg
]
if
!
ok
{
subTree
=
NewTree
()
t
.
fixrouters
[
seg
]
=
subTree
}
subTree
.
addseg
(
segments
[
1
:
],
route
,
wildcards
,
reg
)
}
}
}
// match router to runObject & params
func
(
t
*
Tree
)
Match
(
pattern
string
)
(
runObject
interface
{},
params
map
[
string
]
string
)
{
if
len
(
pattern
)
==
0
||
pattern
[
0
]
!=
'/'
{
return
nil
,
nil
}
return
t
.
match
(
splitPath
(
pattern
),
nil
)
}
func
(
t
*
Tree
)
match
(
segments
[]
string
,
wildcardValues
[]
string
)
(
runObject
interface
{},
params
map
[
string
]
string
)
{
// Handle leaf nodes:
if
len
(
segments
)
==
0
{
if
t
.
leaf
!=
nil
{
if
ok
,
pa
:=
t
.
leaf
.
match
(
wildcardValues
);
ok
{
return
t
.
leaf
.
runObject
,
pa
}
}
return
nil
,
nil
}
var
seg
string
seg
,
segments
=
segments
[
0
],
segments
[
1
:
]
subTree
,
ok
:=
t
.
fixrouters
[
seg
]
if
ok
{
runObject
,
params
=
subTree
.
match
(
segments
,
wildcardValues
)
}
if
runObject
==
nil
&&
t
.
wildcard
!=
nil
{
runObject
,
params
=
t
.
wildcard
.
match
(
segments
,
append
(
wildcardValues
,
seg
))
}
if
runObject
==
nil
{
if
t
.
leaf
!=
nil
{
if
ok
,
pa
:=
t
.
leaf
.
match
(
append
(
wildcardValues
,
seg
));
ok
{
return
t
.
leaf
.
runObject
,
pa
}
}
}
return
runObject
,
params
}
type
leafInfo
struct
{
// names of wildcards that lead to this leaf. eg, ["id" "name"] for the wildcard ":id" and ":name"
wildcards
[]
string
// if the leaf is regexp
regexps
*
regexp
.
Regexp
runObject
interface
{}
}
func
(
leaf
*
leafInfo
)
match
(
wildcardValues
[]
string
)
(
ok
bool
,
params
map
[
string
]
string
)
{
if
leaf
.
regexps
==
nil
{
// has error
if
len
(
wildcardValues
)
==
0
&&
len
(
leaf
.
wildcards
)
>
0
{
if
utils
.
InSlice
(
":"
,
leaf
.
wildcards
)
{
return
true
,
nil
}
Error
(
"bug of router"
)
return
false
,
nil
}
else
if
len
(
wildcardValues
)
==
0
{
// static path
return
true
,
nil
}
// match *
if
len
(
leaf
.
wildcards
)
==
1
&&
leaf
.
wildcards
[
0
]
==
":splat"
{
params
=
make
(
map
[
string
]
string
)
params
[
":splat"
]
=
path
.
Join
(
wildcardValues
...
)
return
true
,
params
}
// match *.*
if
len
(
leaf
.
wildcards
)
==
3
&&
leaf
.
wildcards
[
0
]
==
"."
{
params
=
make
(
map
[
string
]
string
)
lastone
:=
wildcardValues
[
len
(
wildcardValues
)
-
1
]
strs
:=
strings
.
SplitN
(
lastone
,
"."
,
2
)
if
len
(
strs
)
==
2
{
params
[
":ext"
]
=
strs
[
1
]
}
else
{
params
[
":ext"
]
=
""
}
params
[
":path"
]
=
path
.
Join
(
wildcardValues
[
:
len
(
wildcardValues
)
-
1
]
...
)
+
"/"
+
strs
[
0
]
return
true
,
params
}
// match :id
params
=
make
(
map
[
string
]
string
)
j
:=
0
for
_
,
v
:=
range
leaf
.
wildcards
{
if
v
==
":"
{
continue
}
params
[
v
]
=
wildcardValues
[
j
]
j
+=
1
}
if
len
(
params
)
!=
len
(
wildcardValues
)
{
Error
(
"bug of router"
)
return
false
,
nil
}
return
true
,
params
}
if
!
leaf
.
regexps
.
MatchString
(
strings
.
Join
(
wildcardValues
,
""
))
{
return
false
,
nil
}
params
=
make
(
map
[
string
]
string
)
matches
:=
leaf
.
regexps
.
FindStringSubmatch
(
strings
.
Join
(
wildcardValues
,
""
))
for
i
,
match
:=
range
matches
[
1
:
]
{
params
[
leaf
.
wildcards
[
i
]]
=
match
}
return
true
,
params
}
// "/" -> []
// "/admin" -> ["admin"]
// "/admin/" -> ["admin"]
// "/admin/users" -> ["admin", "users"]
func
splitPath
(
key
string
)
[]
string
{
elements
:=
strings
.
Split
(
key
,
"/"
)
if
elements
[
0
]
==
""
{
elements
=
elements
[
1
:
]
}
if
elements
[
len
(
elements
)
-
1
]
==
""
{
elements
=
elements
[
:
len
(
elements
)
-
1
]
}
return
elements
}
// "admin" -> false, nil, ""
// ":id" -> true, [:id], ""
// "?:id" -> true, [: id], "" : meaning can empty
// ":id:int" -> true, [:id], ([0-9]+)
// ":name:string" -> true, [:name], ([\w]+)
// ":id([0-9]+)" -> true, [:id], ([0-9]+)
// ":id([0-9]+)_:name" -> true, [:id :name], ([0-9]+)_(.+)
// "cms_:id_:page.html" -> true, [:id :page], cms_(.+)_(.+).html
// "*" -> true, [:splat], ""
// "*.*" -> true,[. :path :ext], "" . meaning separator
func
splitSegment
(
key
string
)
(
bool
,
[]
string
,
string
)
{
if
strings
.
HasPrefix
(
key
,
"*"
)
{
if
key
==
"*.*"
{
return
true
,
[]
string
{
"."
,
":path"
,
":ext"
},
""
}
else
{
return
true
,
[]
string
{
":splat"
},
""
}
}
if
strings
.
ContainsAny
(
key
,
":"
)
{
var
paramsNum
int
var
out
[]
rune
var
start
bool
var
startexp
bool
var
param
[]
rune
var
expt
[]
rune
var
skipnum
int
params
:=
[]
string
{}
reg
:=
regexp
.
MustCompile
(
`[a-zA-Z0-9]+`
)
for
i
,
v
:=
range
key
{
if
skipnum
>
0
{
skipnum
-=
1
continue
}
if
start
{
//:id:int and :name:string
if
v
==
':'
{
if
len
(
key
)
>=
i
+
4
{
if
key
[
i
+
1
:
i
+
4
]
==
"int"
{
out
=
append
(
out
,
[]
rune
(
"([0-9]+)"
)
...
)
params
=
append
(
params
,
":"
+
string
(
param
))
start
=
false
startexp
=
false
skipnum
=
3
param
=
make
([]
rune
,
0
)
paramsNum
+=
1
continue
}
}
if
len
(
key
)
>=
i
+
7
{
if
key
[
i
+
1
:
i
+
7
]
==
"string"
{
out
=
append
(
out
,
[]
rune
(
`([\w]+)`
)
...
)
params
=
append
(
params
,
":"
+
string
(
param
))
paramsNum
+=
1
start
=
false
startexp
=
false
skipnum
=
6
param
=
make
([]
rune
,
0
)
continue
}
}
}
// params only support a-zA-Z0-9
if
reg
.
MatchString
(
string
(
v
))
{
param
=
append
(
param
,
v
)
continue
}
if
v
!=
'('
{
out
=
append
(
out
,
[]
rune
(
`(.+)`
)
...
)
params
=
append
(
params
,
":"
+
string
(
param
))
param
=
make
([]
rune
,
0
)
paramsNum
+=
1
start
=
false
startexp
=
false
}
}
if
startexp
{
if
v
!=
')'
{
expt
=
append
(
expt
,
v
)
continue
}
}
if
v
==
':'
{
param
=
make
([]
rune
,
0
)
start
=
true
}
else
if
v
==
'('
{
startexp
=
true
start
=
false
params
=
append
(
params
,
":"
+
string
(
param
))
paramsNum
+=
1
expt
=
make
([]
rune
,
0
)
expt
=
append
(
expt
,
'('
)
}
else
if
v
==
')'
{
startexp
=
false
expt
=
append
(
expt
,
')'
)
out
=
append
(
out
,
expt
...
)
param
=
make
([]
rune
,
0
)
}
else
if
v
==
'?'
{
params
=
append
(
params
,
":"
)
}
else
{
out
=
append
(
out
,
v
)
}
}
if
len
(
param
)
>
0
{
if
paramsNum
>
0
{
out
=
append
(
out
,
[]
rune
(
`(.+)`
)
...
)
}
params
=
append
(
params
,
":"
+
string
(
param
))
}
return
true
,
params
,
string
(
out
)
}
else
{
return
false
,
nil
,
""
}
}
tree_test.go
0 → 100644
View file @
bfabcfcb
package
beego
import
"testing"
type
testinfo
struct
{
url
string
requesturl
string
params
map
[
string
]
string
}
var
routers
[]
testinfo
func
init
()
{
routers
=
make
([]
testinfo
,
0
)
routers
=
append
(
routers
,
testinfo
{
"/:id"
,
"/123"
,
map
[
string
]
string
{
":id"
:
"123"
}})
routers
=
append
(
routers
,
testinfo
{
"/"
,
"/"
,
nil
})
routers
=
append
(
routers
,
testinfo
{
"/customer/login"
,
"/customer/login"
,
nil
})
routers
=
append
(
routers
,
testinfo
{
"/*"
,
"/customer/123"
,
map
[
string
]
string
{
":splat"
:
"customer/123"
}})
routers
=
append
(
routers
,
testinfo
{
"/*.*"
,
"/nice/api.json"
,
map
[
string
]
string
{
":path"
:
"nice/api"
,
":ext"
:
"json"
}})
routers
=
append
(
routers
,
testinfo
{
"/v1/shop/:id:int"
,
"/v1/shop/123"
,
map
[
string
]
string
{
":id"
:
"123"
}})
routers
=
append
(
routers
,
testinfo
{
"/v1/shop/:id/:name"
,
"/v1/shop/123/nike"
,
map
[
string
]
string
{
":id"
:
"123"
,
":name"
:
"nike"
}})
routers
=
append
(
routers
,
testinfo
{
"/v1/shop/:id/account"
,
"/v1/shop/123/account"
,
map
[
string
]
string
{
":id"
:
"123"
}})
routers
=
append
(
routers
,
testinfo
{
"/v1/shop/:name:string"
,
"/v1/shop/nike"
,
map
[
string
]
string
{
":name"
:
"nike"
}})
routers
=
append
(
routers
,
testinfo
{
"/v1/shop/:id([0-9]+)"
,
"/v1/shop//123"
,
map
[
string
]
string
{
":id"
:
"123"
}})
routers
=
append
(
routers
,
testinfo
{
"/v1/shop/:id([0-9]+)_:name"
,
"/v1/shop/123_nike"
,
map
[
string
]
string
{
":id"
:
"123"
,
":name"
:
"nike"
}})
routers
=
append
(
routers
,
testinfo
{
"/v1/shop/:id_cms.html"
,
"/v1/shop/123_cms.html"
,
map
[
string
]
string
{
":id"
:
"123"
}})
routers
=
append
(
routers
,
testinfo
{
"/v1/shop/cms_:id_:page.html"
,
"/v1/shop/cms_123_1.html"
,
map
[
string
]
string
{
":id"
:
"123"
,
":page"
:
"1"
}})
}
func
TestTreeRouters
(
t
*
testing
.
T
)
{
for
_
,
r
:=
range
routers
{
tr
:=
NewTree
()
tr
.
AddRouter
(
r
.
url
,
"astaxie"
)
obj
,
param
:=
tr
.
Match
(
r
.
requesturl
)
if
obj
==
nil
||
obj
.
(
string
)
!=
"astaxie"
{
t
.
Fatal
(
r
.
url
+
" can't get obj "
)
}
if
r
.
params
!=
nil
{
for
k
,
v
:=
range
r
.
params
{
if
vv
,
ok
:=
param
[
k
];
!
ok
{
t
.
Fatal
(
r
.
url
+
r
.
requesturl
+
" get param empty:"
+
k
)
}
else
if
vv
!=
v
{
t
.
Fatal
(
r
.
url
+
" "
+
r
.
requesturl
+
" should be:"
+
v
+
" get param:"
+
vv
)
}
}
}
}
}
func
TestSplitPath
(
t
*
testing
.
T
)
{
a
:=
splitPath
(
"/"
)
if
len
(
a
)
!=
0
{
t
.
Fatal
(
"/ should retrun []"
)
}
a
=
splitPath
(
"/admin"
)
if
len
(
a
)
!=
1
||
a
[
0
]
!=
"admin"
{
t
.
Fatal
(
"/admin should retrun [admin]"
)
}
a
=
splitPath
(
"/admin/"
)
if
len
(
a
)
!=
1
||
a
[
0
]
!=
"admin"
{
t
.
Fatal
(
"/admin/ should retrun [admin]"
)
}
a
=
splitPath
(
"/admin/users"
)
if
len
(
a
)
!=
2
||
a
[
0
]
!=
"admin"
||
a
[
1
]
!=
"users"
{
t
.
Fatal
(
"/admin should retrun [admin users]"
)
}
a
=
splitPath
(
"/admin/:id:int"
)
if
len
(
a
)
!=
2
||
a
[
0
]
!=
"admin"
||
a
[
1
]
!=
":id:int"
{
t
.
Fatal
(
"/admin should retrun [admin :id:int]"
)
}
}
func
TestSplitSegment
(
t
*
testing
.
T
)
{
b
,
w
,
r
:=
splitSegment
(
"admin"
)
if
b
||
len
(
w
)
!=
0
||
r
!=
""
{
t
.
Fatal
(
"admin should return false, nil, ''"
)
}
b
,
w
,
r
=
splitSegment
(
"*"
)
if
!
b
||
len
(
w
)
!=
1
||
w
[
0
]
!=
":splat"
||
r
!=
""
{
t
.
Fatal
(
"* should return true, [:splat], ''"
)
}
b
,
w
,
r
=
splitSegment
(
"*.*"
)
if
!
b
||
len
(
w
)
!=
3
||
w
[
1
]
!=
":path"
||
w
[
2
]
!=
":ext"
||
w
[
0
]
!=
"."
||
r
!=
""
{
t
.
Fatal
(
"admin should return true,[. :path :ext], ''"
)
}
b
,
w
,
r
=
splitSegment
(
":id"
)
if
!
b
||
len
(
w
)
!=
1
||
w
[
0
]
!=
":id"
||
r
!=
""
{
t
.
Fatal
(
":id should return true, [:id], ''"
)
}
b
,
w
,
r
=
splitSegment
(
"?:id"
)
if
!
b
||
len
(
w
)
!=
2
||
w
[
0
]
!=
":"
||
w
[
1
]
!=
":id"
||
r
!=
""
{
t
.
Fatal
(
"?:id should return true, [: :id], ''"
)
}
b
,
w
,
r
=
splitSegment
(
":id:int"
)
if
!
b
||
len
(
w
)
!=
1
||
w
[
0
]
!=
":id"
||
r
!=
"([0-9]+)"
{
t
.
Fatal
(
":id:int should return true, [:id], '([0-9]+)'"
)
}
b
,
w
,
r
=
splitSegment
(
":name:string"
)
if
!
b
||
len
(
w
)
!=
1
||
w
[
0
]
!=
":name"
||
r
!=
`([\w]+)`
{
t
.
Fatal
(
`:name:string should return true, [:name], '([\w]+)'`
)
}
b
,
w
,
r
=
splitSegment
(
":id([0-9]+)"
)
if
!
b
||
len
(
w
)
!=
1
||
w
[
0
]
!=
":id"
||
r
!=
`([0-9]+)`
{
t
.
Fatal
(
`:id([0-9]+) should return true, [:id], '([0-9]+)'`
)
}
b
,
w
,
r
=
splitSegment
(
":id([0-9]+)_:name"
)
if
!
b
||
len
(
w
)
!=
2
||
w
[
0
]
!=
":id"
||
w
[
1
]
!=
":name"
||
r
!=
`([0-9]+)_(.+)`
{
t
.
Fatal
(
`:id([0-9]+)_:name should return true, [:id :name], '([0-9]+)_(.+)'`
)
}
b
,
w
,
r
=
splitSegment
(
":id_cms.html"
)
if
!
b
||
len
(
w
)
!=
1
||
w
[
0
]
!=
":id"
||
r
!=
`(.+)_cms.html`
{
t
.
Fatal
(
":id_cms.html should return true, [:id], '(.+)_cms.html'"
)
}
b
,
w
,
r
=
splitSegment
(
"cms_:id_:page.html"
)
if
!
b
||
len
(
w
)
!=
2
||
w
[
0
]
!=
":id"
||
w
[
1
]
!=
":page"
||
r
!=
`cms_(.+)_(.+).html`
{
t
.
Fatal
(
":id_cms.html should return true, [:id :page], cms_(.+)_(.+).html"
)
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment