-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathfsrouter.go
132 lines (102 loc) · 2.67 KB
/
fsrouter.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
package fsrouter
import (
"fmt"
"path/filepath"
"regexp"
"sort"
"strings"
"github.com/mattn/go-zglob"
)
var (
paramRegex = regexp.MustCompile(`\[((?:\.\.\.)?([a-zA-Z0-9]+))\]`)
paramRegexSingle = regexp.MustCompile(`\[([a-zA-Z0-9]+)\]`)
paramRegexNested = regexp.MustCompile(`\[\.\.\.([a-zA-Z0-9]+)\]`)
)
type Preset struct {
NamedParamReplacement string
WildcardReplacement string
}
var FiberPreset = Preset{
NamedParamReplacement: ":$1",
WildcardReplacement: "*",
}
var ChiPreset = Preset{
NamedParamReplacement: "{$1}",
WildcardReplacement: "*",
}
type FSRouter struct {
Root string
IncludePattern string
Preset Preset
}
func New(rootDir string, preset Preset) *FSRouter {
return &FSRouter{
Root: rootDir,
IncludePattern: "**/*.html",
Preset: preset,
}
}
type RouteParam struct {
Name string
Nested bool
}
type Route struct {
Name string
ParamNames []RouteParam
Path string
}
func (r Route) Realize(params map[string]string) string {
path := r.Name
for _, pn := range r.ParamNames {
if pn.Nested {
path = strings.ReplaceAll(path, fmt.Sprintf("[...%s]", pn.Name), params[pn.Name])
} else {
path = strings.ReplaceAll(path, fmt.Sprintf("[%s]", pn.Name), params[pn.Name])
}
}
return path
}
func (r Route) ExtractMap(valueFn func(param string) string) map[string]string {
m := map[string]string{}
for _, pn := range r.ParamNames {
m[pn.Name] = valueFn(pn.Name)
}
return m
}
func (fsr FSRouter) LoadRoutes() ([]Route, error) {
matches, err := zglob.Glob(filepath.Join(fsr.Root, fsr.IncludePattern))
if err != nil {
return nil, err
}
sort.Strings(matches)
routes := make([]Route, len(matches))
for i, path := range matches {
relPath, err := filepath.Rel(fsr.Root, path)
if err != nil {
return nil, err
}
routes[i] = fsr.parseRoute(relPath)
}
return routes, nil
}
func (fsr FSRouter) parseRoute(path string) Route {
paramNames := []RouteParam{}
allParamMatches := paramRegex.FindAllStringSubmatch(path, -1)
for _, paramMatch := range allParamMatches {
paramNames = append(paramNames, RouteParam{
Name: paramMatch[2],
Nested: strings.HasPrefix(paramMatch[1], "..."),
})
}
// apply route replacement using the current preset
route := "/" + path
route = paramRegexSingle.ReplaceAllString(route, fsr.Preset.NamedParamReplacement)
route = paramRegexNested.ReplaceAllString(route, fsr.Preset.WildcardReplacement)
// apply common replacements to the url
if strings.HasSuffix(route, "index.html") {
route = strings.TrimSuffix(route, "index.html")
} else if strings.HasSuffix(route, ".html") {
route = strings.TrimSuffix(route, ".html")
}
return Route{route, paramNames, path}
}