Skip to content

Commit 51bdb07

Browse files
committed
Add native function to library
1 parent c6bdd1f commit 51bdb07

File tree

3 files changed

+219
-42
lines changed

3 files changed

+219
-42
lines changed

README.md

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ import pathToRegexp "github.com/soongo/path-to-regexp"
1919
// pathToRegexp.Parse(path, options) // options can be nil
2020
// pathToRegexp.Compile(path, options) // options can be nil
2121
// pathToRegexp.MustCompile(path, options) // like Compile but panics if the error is non-nil
22+
// pathToRegexp.Match(path, options) // options can be nil
23+
// pathToRegexp.MustMatch(path, options) // like Match but panics if the error is non-nil
2224
// pathToRegexp.Must(regexp, err) // wraps a call to a function returning (*regexp2.Regexp, error) and panics if the error is non-nil.
2325
```
2426

@@ -39,6 +41,8 @@ import pathToRegexp "github.com/soongo/path-to-regexp"
3941
- **Delimiter** The default delimiter for segments. (default: `'/'`)
4042
- **EndsWith** Optional character, or list of characters, to treat as "end" characters.
4143
- **Whitelist** List of characters to consider delimiters when parsing. (default: `nil`, any character)
44+
- **Encode** How to encode uri. (default: `pathToRegexp.EncodeURIComponent`)
45+
- **Encode** How to decode uri. (default: `pathToRegexp.DecodeURIComponent`)
4246

4347
```go
4448
var tokens []pathToRegexp.Token
@@ -200,9 +204,22 @@ fmt.Println(match)
200204

201205
**Tip:** Backslashes need to be escaped with another backslash in Go strings.
202206

207+
### Match
208+
209+
The `match` function will return a function for transforming paths into parameters:
210+
211+
```go
212+
match := pathToRegexp.MustMatch("/user/:id")
213+
214+
fmt.Printf("%#v\n", match("/user/123"))
215+
//=> &pathtoregexp.MatchResult{Path:"/user/123", Index:0, Params:map[interface {}]interface {}{"id":"123"}}
216+
217+
match("/invalid") //=> nil
218+
```
219+
203220
### Parse
204221

205-
The parse function is exposed via `pathToRegexp.Parse`. This will return a slice of strings and tokens.
222+
The `Parse` function will return a list of strings and tokens from a path string:
206223

207224
```go
208225
tokens := pathToRegexp.Parse("/route/:foo/(.*)", nil)
@@ -221,7 +238,7 @@ fmt.Printf("%#v\n", tokens[2])
221238

222239
### Compile ("Reverse" Path-To-RegExp)
223240

224-
Path-To-RegExp exposes a compile function for transforming a string into a valid path.
241+
The `Compile` function will return a function for transforming parameters into a valid path:
225242

226243
```go
227244
falseValue := false

path_to_regexp.go

Lines changed: 100 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,21 @@ type Options struct {
6565

6666
// how to encode uri
6767
Encode func(uri string, token interface{}) string
68+
69+
// how to decode uri
70+
Decode func(str string, token interface{}) string
71+
}
72+
73+
// MatchResult contains the result of match function
74+
type MatchResult struct {
75+
// matched url path
76+
Path string
77+
78+
// matched start index
79+
Index int
80+
81+
// matched params in url
82+
Params map[interface{}]interface{}
6883
}
6984

7085
// defaultDelimiter is the default delimiter of path.
@@ -174,13 +189,76 @@ func Compile(str string, o *Options) (func(interface{}, *Options) string, error)
174189
// MustCompile is like Compile but panics if the expression cannot be compiled.
175190
// It simplifies safe initialization of global variables.
176191
func MustCompile(str string, o *Options) func(interface{}, *Options) string {
177-
f, err := tokensToFunction(Parse(str, o), o)
192+
f, err := Compile(str, o)
178193
if err != nil {
179194
panic(`pathtoregexp: Compile(` + quote(str) + `): ` + err.Error())
180195
}
181196
return f
182197
}
183198

199+
// Match creates path match function from `path-to-regexp` spec.
200+
func Match(path interface{}, o *Options) (func(string, *Options) *MatchResult, error) {
201+
var tokens []Token
202+
re, err := PathToRegexp(path, &tokens, o)
203+
if err != nil {
204+
return nil, err
205+
}
206+
207+
return regexpToFunction(re, tokens), nil
208+
}
209+
210+
// MustMatch is like Match but panics if err occur in match function.
211+
func MustMatch(path interface{}, o *Options) func(string, *Options) *MatchResult {
212+
f, err := Match(path, o)
213+
if err != nil {
214+
panic(err)
215+
}
216+
return f
217+
}
218+
219+
// Create a path match function from `path-to-regexp` output.
220+
func regexpToFunction(re *regexp2.Regexp, tokens []Token) func(string, *Options) *MatchResult {
221+
return func(pathname string, options *Options) *MatchResult {
222+
m, err := re.FindStringMatch(pathname)
223+
if m == nil || m.GroupCount() == 0 || err != nil {
224+
return nil
225+
}
226+
227+
path := m.Groups()[0].String()
228+
index := m.Index
229+
params := make(map[interface{}]interface{})
230+
decode := DecodeURIComponent
231+
if options != nil && options.Decode != nil {
232+
decode = options.Decode
233+
}
234+
235+
for i := 1; i < m.GroupCount(); i++ {
236+
group := m.Groups()[i]
237+
if len(group.Captures) == 0 {
238+
continue
239+
}
240+
241+
token := tokens[i-1]
242+
matchedStr := group.String()
243+
244+
if token.Repeat {
245+
arr := strings.Split(matchedStr, token.Delimiter)
246+
length := len(arr)
247+
if length > 0 {
248+
for i, str := range arr {
249+
arr[i] = decode(str, token)
250+
}
251+
params[token.Name] = arr
252+
}
253+
} else {
254+
params[token.Name] = decode(matchedStr, token)
255+
}
256+
}
257+
258+
return &MatchResult{Path: path, Index: index, Params: params}
259+
}
260+
}
261+
184262
// Expose a method for transforming tokens into the path function.
185263
func tokensToFunction(tokens []interface{}, o *Options) (
186264
func(interface{}, *Options) string, error) {
@@ -200,7 +278,7 @@ func tokensToFunction(tokens []interface{}, o *Options) (
200278

201279
return func(data interface{}, o *Options) string {
202280
t := true
203-
path, validate, encode := "", &t, encodeURIComponent
281+
path, validate, encode := "", &t, EncodeURIComponent
204282
if o != nil {
205283
if o.Encode != nil {
206284
encode = o.Encode
@@ -355,12 +433,20 @@ func toMap(data interface{}) map[interface{}]interface{} {
355433
return m
356434
}
357435

358-
func encodeURIComponent(str string, token interface{}) string {
436+
func EncodeURIComponent(str string, token interface{}) string {
359437
r := url.QueryEscape(str)
360438
r = strings.Replace(r, "+", "%20", -1)
361439
return r
362440
}
363441

442+
func DecodeURIComponent(str string, token interface{}) string {
443+
r, err := url.QueryUnescape(str)
444+
if err != nil {
445+
panic(err)
446+
}
447+
return r
448+
}
449+
364450
// Escape a regular expression string.
365451
func escapeString(str string) string {
366452
str, err := escapeRegexp.Replace(str, "\\$1", -1, -1)
@@ -408,11 +494,16 @@ func Must(r *regexp2.Regexp, err error) *regexp2.Regexp {
408494
// Pull out tokens from a regexp.
409495
func regexpToRegexp(path *regexp2.Regexp, tokens *[]Token) *regexp2.Regexp {
410496
if tokens != nil {
411-
m, _ := tokenRegexp.FindStringMatch(path.String())
412-
if m != nil && m.GroupCount() > 0 {
413-
newTokens := make([]Token, 0, len(*tokens)+m.GroupCount())
414-
newTokens = append(newTokens, *tokens...)
415-
for i := 0; i < m.GroupCount(); i++ {
497+
totalGroupCount := 0
498+
for m, _ := tokenRegexp.FindStringMatch(path.String()); m != nil; m,
499+
_ = tokenRegexp.FindNextMatch(m) {
500+
totalGroupCount += m.GroupCount()
501+
}
502+
503+
if totalGroupCount > 0 {
504+
newTokens := append(make([]Token, 0), *tokens...)
505+
506+
for i := 0; i < totalGroupCount; i++ {
416507
newTokens = append(newTokens, Token{
417508
Name: i,
418509
Prefix: "",
@@ -422,6 +513,7 @@ func regexpToRegexp(path *regexp2.Regexp, tokens *[]Token) *regexp2.Regexp {
422513
Pattern: "",
423514
})
424515
}
516+
425517
hdr := (*reflect.SliceHeader)(unsafe.Pointer(tokens))
426518
*hdr = *(*reflect.SliceHeader)(unsafe.Pointer(&newTokens))
427519
}

0 commit comments

Comments
 (0)