Skip to content

Commit efa6414

Browse files
committed
Add two helpers: MustCompile & Must, and make some optimizations
1 parent ce68c2f commit efa6414

File tree

2 files changed

+56
-25
lines changed

2 files changed

+56
-25
lines changed

README.md

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ import pathToRegexp "github.com/soongo/path-to-regexp"
1818
// pathToRegexp.PathToRegexp(path, tokens, options) // tokens and options can be nil
1919
// pathToRegexp.Parse(path, options) // options can be nil
2020
// pathToRegexp.Compile(path, options) // options can be nil
21+
// pathToRegexp.MustCompile(path, options) // like Compile but panics if the error is non-nil
22+
// pathToRegexp.Must(regexp, err) // wraps a call to a function returning (*regexp2.Regexp, error) and panics if the error is non-nil.
2123
```
2224

2325
- **path** A string, array or slice of strings, or a regular expression with type *github.com/dlclark/regexp2.Regexp.
@@ -40,7 +42,7 @@ import pathToRegexp "github.com/soongo/path-to-regexp"
4042

4143
```go
4244
var tokens []pathToRegexp.Token
43-
regexp, err := pathToRegexp.PathToRegexp("/foo/:bar", &tokens, nil)
45+
regexp := pathToRegexp.Must(pathToRegexp.PathToRegexp("/foo/:bar", &tokens, nil))
4446
// regexp: ^\/foo\/([^\/]+?)(?:\/)?$
4547
// tokens: [{name:"bar", prefix:"/", delimiter:"/", optional:false, repeat:false, pattern:"[^\\/]+?"}}]
4648
```
@@ -56,7 +58,7 @@ The path argument is used to define parameters and populate the list of tokens.
5658
Named parameters are defined by prefixing a colon to the parameter name (`:foo`). By default, the parameter will match until the next prefix (e.g. `[^/]+`).
5759

5860
```go
59-
regexp, err := pathToRegexp.PathToRegexp("/:foo/:bar", nil, nil)
61+
regexp := pathToRegexp.Must(pathToRegexp.PathToRegexp("/:foo/:bar", nil, nil))
6062
// tokens: [
6163
// {name:"foo", prefix:"/", delimiter:"/", optional:false, repeat:false, pattern:"[^\\/]+?"},
6264
// {name:"bar", prefix:"/", delimiter:"/", optional:false, repeat:false, pattern:"[^\\/]+?"}
@@ -79,7 +81,7 @@ fmt.Printf("%d, %q\n", match.Index, match)
7981
Parameters can be suffixed with a question mark (`?`) to make the parameter optional.
8082

8183
```go
82-
regexp, err := pathToRegexp.PathToRegexp("/:foo/:bar?", nil, nil)
84+
regexp := pathToRegexp.Must(pathToRegexp.PathToRegexp("/:foo/:bar?", nil, nil))
8385
// tokens: [
8486
// {name:"foo", prefix:"/", delimiter:"/", optional:false, repeat:false, pattern:"[^\\/]+?"},
8587
// {name:"bar", prefix:"/", delimiter:"/", optional:true, repeat:false, pattern:"[^\\/]+?"}
@@ -107,7 +109,7 @@ fmt.Printf("%d, %q\n", match.Index, match)
107109
Parameters can be suffixed with an asterisk (`*`) to denote a zero or more parameter matches. The prefix is used for each match.
108110

109111
```go
110-
regexp, err := pathToRegexp.PathToRegexp("/:foo*", nil, nil)
112+
regexp := pathToRegexp.Must(pathToRegexp.PathToRegexp("/:foo*", nil, nil))
111113
// tokens: [{name:"foo", prefix:"/", delimiter:"/", optional:true, repeat:true, pattern:"[^\\/]+?"}]
112114

113115
match, err := regexp.FindStringMatch("/")
@@ -130,7 +132,7 @@ fmt.Printf("%d, %q\n", match.Index, match)
130132
Parameters can be suffixed with a plus sign (`+`) to denote a one or more parameter matches. The prefix is used for each match.
131133

132134
```go
133-
regexp, err := pathToRegexp.PathToRegexp("/:foo+", nil, nil)
135+
regexp := pathToRegexp.Must(pathToRegexp.PathToRegexp("/:foo+", nil, nil))
134136
// tokens: [{name:"foo", prefix:"/", delimiter:"/", optional:false, repeat:true, pattern:"[^\\/]+?"}]
135137

136138
match, err := regexp.FindStringMatch("/")
@@ -150,7 +152,7 @@ fmt.Printf("%d, %q\n", match.Index, match)
150152
It is possible to write an unnamed parameter that only consists of a matching group. It works the same as a named parameter, except it will be numerically indexed.
151153

152154
```go
153-
regexp, err := pathToRegexp.PathToRegexp("/:foo/(.*)", nil, nil)
155+
regexp := pathToRegexp.Must(pathToRegexp.PathToRegexp("/:foo/(.*)", nil, nil))
154156
// tokens: [
155157
// {name:"foo", prefix:"/", delimiter:"/", optional:false, repeat:false, pattern:"[^\\/]+?"},
156158
// {name:0, prefix:"/", delimiter:"/", optional:false, repeat:false, pattern:".*"}
@@ -169,7 +171,7 @@ fmt.Printf("%d, %q\n", match.Index, match)
169171
All parameters can have a custom regexp, which overrides the default match (`[^/]+`). For example, you can match digits or names in a path:
170172

171173
```go
172-
regexpNumbers, err := pathToRegexp.PathToRegexp("/icon-:foo(\\d+).png", nil, nil)
174+
regexpNumbers := pathToRegexp.Must(pathToRegexp.PathToRegexp("/icon-:foo(\\d+).png", nil, nil))
173175
// tokens: {name:"foo", prefix:"-", delimiter:"-", optional:false, repeat:false, pattern:"\\d+"}
174176

175177
match, err := regexpNumbers.FindStringMatch("/icon-123.png")
@@ -182,7 +184,7 @@ match, err = regexpNumbers.FindStringMatch("/icon-abc.png")
182184
fmt.Println(match)
183185
//=> nil
184186

185-
regexpWord, err := pathToRegexp.PathToRegexp("/(user|u)", nil, nil)
187+
regexpWord := pathToRegexp.Must(pathToRegexp.PathToRegexp("/(user|u)", nil, nil))
186188
// tokens: {name:0, prefix:"/", delimiter:"/", optional:false, repeat:false, pattern:"user|u"}
187189

188190
match, err = regexpWord.FindStringMatch("/u")
@@ -222,7 +224,7 @@ fmt.Printf("%#v\n", tokens[2])
222224
Path-To-RegExp exposes a compile function for transforming a string into a valid path.
223225

224226
```go
225-
toPath, err := pathToRegexp.Compile("/user/:id", nil)
227+
toPath := pathToRegexp.MustCompile("/user/:id", nil)
226228

227229
toPath(map[string]int{"id": 123}, nil) //=> "/user/123"
228230
toPath(map[string]string{"id": "café"}, nil) //=> "/user/caf%C3%A9"
@@ -237,12 +239,12 @@ toPath(map[string]string{"id": ":&"}, &Options{encode: func(value string, token
237239
return value
238240
}}) //=> /user/:&
239241

240-
toPathRepeated, err := pathToRegexp.Compile("/:segment+", nil)
242+
toPathRepeated := pathToRegexp.MustCompile("/:segment+", nil)
241243

242244
toPathRepeated(map[string]string{"segment": "foo"}, nil) //=> "/foo"
243245
toPathRepeated(map[string][]string{"segment": {"a", "b", "c"}}, nil) //=> "/a/b/c"
244246

245-
toPathRegexp, err := pathToRegexp.Compile("/user/:id(\\d+)", nil)
247+
toPathRegexp := pathToRegexp.MustCompile("/user/:id(\\d+)", nil)
246248

247249
toPathRegexp(map[string]int{"id": 123}, nil) //=> "/user/123"
248250
toPathRegexp(map[string]string{"id": "123"}, nil) //=> "/user/123"

path_to_regexp.go

Lines changed: 43 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -67,26 +67,30 @@ type Options struct {
6767
encode func(uri string, token interface{}) string
6868
}
6969

70-
// DefaultDelimiter is the default delimiter of path.
71-
const DefaultDelimiter = "/"
70+
// defaultDelimiter is the default delimiter of path.
71+
const defaultDelimiter = "/"
7272

73-
// PathRegexp is the main path matching regexp utility.
74-
var PathRegexp = regexp2.MustCompile(strings.Join([]string{
73+
// pathRegexp is the main path matching regexp utility.
74+
var pathRegexp = regexp2.MustCompile(strings.Join([]string{
7575
"(\\\\.)",
7676
"(?:\\:(\\w+)(?:\\(((?:\\\\.|[^\\\\()])+)\\))?|\\(((?:\\\\.|[^\\\\()])+)\\))([+*?])?",
7777
}, "|"), regexp2.None)
7878

79+
var escapeRegexp = regexp2.MustCompile("([.+*?=^!:${}()[\\]|/\\\\])", regexp2.None)
80+
var escapeGroupRegexp = regexp2.MustCompile("([=!:$/()])", regexp2.None)
81+
var tokenRegexp = regexp2.MustCompile("\\((?!\\?)", regexp2.None)
82+
7983
// Parse a string for the raw tokens.
8084
func Parse(str string, o *Options) []interface{} {
8185
tokens, tokenIndex, index, path, pathEscaped := make([]interface{}, 0), 0, 0, "", false
8286
if o == nil {
8387
o = &Options{}
8488
}
85-
defaultDelimiter := orString(o.delimiter, DefaultDelimiter)
89+
defaultDelimiter := orString(o.delimiter, defaultDelimiter)
8690
whitelist := o.whitelist
8791

88-
for matcher, _ := PathRegexp.FindStringMatch(str); matcher != nil; matcher,
89-
_ = PathRegexp.FindNextMatch(matcher) {
92+
for matcher, _ := pathRegexp.FindStringMatch(str); matcher != nil; matcher,
93+
_ = pathRegexp.FindNextMatch(matcher) {
9094
groups := matcher.Groups()
9195
m := groups[0].String()
9296
escaped := groups[1].String()
@@ -167,6 +171,16 @@ func Compile(str string, o *Options) (func(interface{}, *Options) string, error)
167171
return tokensToFunction(Parse(str, o), o)
168172
}
169173

174+
// MustCompile is like Compile but panics if the expression cannot be compiled.
175+
// It simplifies safe initialization of global variables.
176+
func MustCompile(str string, o *Options) func(interface{}, *Options) string {
177+
f, err := tokensToFunction(Parse(str, o), o)
178+
if err != nil {
179+
panic(`pathtoregexp: Compile(` + quote(str) + `): ` + err.Error())
180+
}
181+
return f
182+
}
183+
170184
// Expose a method for transforming tokens into the path function.
171185
func tokensToFunction(tokens []interface{}, o *Options) (
172186
func(interface{}, *Options) string, error) {
@@ -349,8 +363,7 @@ func encodeURIComponent(str string, token interface{}) string {
349363

350364
// Escape a regular expression string.
351365
func escapeString(str string) string {
352-
str, err := regexp2.MustCompile("([.+*?=^!:${}()[\\]|/\\\\])",
353-
regexp2.None).Replace(str, "\\$1", -1, -1)
366+
str, err := escapeRegexp.Replace(str, "\\$1", -1, -1)
354367
if err != nil {
355368
panic(err)
356369
}
@@ -359,14 +372,20 @@ func escapeString(str string) string {
359372

360373
// Escape the capturing group by escaping special characters and meaning.
361374
func escapeGroup(group string) string {
362-
group, err := regexp2.MustCompile("([=!:$/()])", regexp2.None).Replace(group,
363-
"\\$1", -1, -1)
375+
group, err := escapeGroupRegexp.Replace(group, "\\$1", -1, -1)
364376
if err != nil {
365377
panic(err)
366378
}
367379
return group
368380
}
369381

382+
func quote(s string) string {
383+
if strconv.CanBackquote(s) {
384+
return "`" + s + "`"
385+
}
386+
return strconv.Quote(s)
387+
}
388+
370389
// Get the flags for a regexp from the options.
371390
func flags(o *Options) regexp2.RegexOptions {
372391
if o != nil && o.sensitive {
@@ -375,11 +394,21 @@ func flags(o *Options) regexp2.RegexOptions {
375394
return regexp2.IgnoreCase
376395
}
377396

397+
// Must is a helper that wraps a call to a function returning (*regexp2.Regexp, error)
398+
// and panics if the error is non-nil. It is intended for use in variable initializations
399+
// such as
400+
// var r = pathtoregexp.Must(pathtoregexp.PathToRegexp("/", nil, nil))
401+
func Must(r *regexp2.Regexp, err error) *regexp2.Regexp {
402+
if err != nil {
403+
panic(err)
404+
}
405+
return r
406+
}
407+
378408
// Pull out tokens from a regexp.
379409
func regexpToRegexp(path *regexp2.Regexp, tokens *[]Token) *regexp2.Regexp {
380410
if tokens != nil {
381-
r := regexp2.MustCompile("\\((?!\\?)", regexp2.None)
382-
m, _ := r.FindStringMatch(path.String())
411+
m, _ := tokenRegexp.FindStringMatch(path.String())
383412
if m != nil && m.GroupCount() > 0 {
384413
newTokens := make([]Token, 0, len(*tokens)+m.GroupCount())
385414
newTokens = append(newTokens, *tokens...)
@@ -446,7 +475,7 @@ func tokensToRegExp(rawTokens []interface{}, tokens *[]Token, o *Options) (*rege
446475
}
447476
}
448477

449-
delimiter := orString(o.delimiter, DefaultDelimiter)
478+
delimiter := orString(o.delimiter, defaultDelimiter)
450479
arr := make([]string, len(ends)+1)
451480
for i, v := range ends {
452481
v = escapeString(v)

0 commit comments

Comments
 (0)