Skip to content

Commit 653f912

Browse files
committed
Support {} for capturing prefix and suffix chars
1 parent 9905f2b commit 653f912

File tree

3 files changed

+1106
-1010
lines changed

3 files changed

+1106
-1010
lines changed

README.md

Lines changed: 108 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -45,181 +45,207 @@ import pathToRegexp "github.com/soongo/path-to-regexp"
4545
- **tokens** An array to populate with tokens found in the path.
4646
- token
4747
- **Name** The name of the token (`string` for named or `number` for index)
48-
- **Prefix** The prefix character for the segment (e.g. `/`)
49-
- **Delimiter** The delimiter for the segment (same as prefix or default delimiter)
50-
- **Optional** Indicates the token is optional (`boolean`)
51-
- **Repeat** Indicates the token is repeated (`boolean`)
48+
- **Prefix** The prefix string for the segment (e.g. `"/"`)
49+
- **Suffix** The suffix string for the segment (e.g. `""`)
5250
- **Pattern** The RegExp used to match this token (`string`)
51+
- **Modifier** The modifier character used for the segment (e.g. `?`)
5352
- **options**
5453
- **Sensitive** When `true` the regexp will be case sensitive. (default: `false`)
5554
- **Strict** When `true` the regexp allows an optional trailing delimiter to match. (default: `false`)
5655
- **End** When `true` the regexp will match to the end of the string. (default: `true`)
5756
- **Start** When `true` the regexp will match from the beginning of the string. (default: `true`)
58-
- **Delimiter** The default delimiter for segments. (default: `'/'`)
57+
- **Validate** When `false` the function can produce an invalid (unmatched) path. (default: `true`)
58+
- **Delimiter** The default delimiter for segments, e.g. `[^/]` for `:named` patterns. (default: `'/'`)
5959
- **EndsWith** Optional character, or list of characters, to treat as "end" characters.
60-
- **Whitelist** List of characters to consider delimiters when parsing. (default: `nil`, any character)
60+
- **prefixes** List of characters to automatically consider prefixes when parsing. (default: `./`)
6161
- **Encode** How to encode uri. (default: `func (uri string, token interface{}) string { return uri }`)
6262
- **Decode** How to decode uri. (default: `func (uri string, token interface{}) string { return uri }`)
6363

6464
```go
6565
var tokens []pathToRegexp.Token
6666
regexp := pathToRegexp.Must(pathToRegexp.PathToRegexp("/foo/:bar", &tokens, nil))
67-
// regexp: ^\/foo\/([^\/]+?)(?:\/)?$
68-
// tokens: [{Name:"bar", Prefix:"/", Delimiter:"/", Optional:false, Repeat:false, Pattern:"[^\\/]+?"}}]
67+
// regexp: ^\/foo(?:\/([^\/]+?))[\/]?(?=$)
68+
// tokens: [{Name:"bar", Prefix:"/", Suffix:"", Pattern:"[^\\/]+?", Modifier:""}]
6969
```
7070

7171
**Please note:** The `Regexp` returned by `path-to-regexp` is intended for ordered data (e.g. pathnames, hostnames). It can not handle arbitrarily ordered data (e.g. query strings, URL fragments, JSON, etc).
7272

7373
### Parameters
7474

75-
The path argument is used to define parameters and populate the list of tokens.
75+
The path argument is used to define parameters and populate tokens.
7676

7777
#### Named Parameters
7878

79-
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. `[^/]+`).
79+
Named parameters are defined by prefixing a colon to the parameter name (`:foo`).
8080

8181
```go
82-
regexp := pathToRegexp.Must(pathToRegexp.PathToRegexp("/:foo/:bar", nil, nil))
82+
var tokens []pathToRegexp.Token
83+
regexp := pathToRegexp.Must(pathToRegexp.PathToRegexp("/:foo/:bar", &tokens, nil))
8384
// tokens: [
84-
// {Name:"foo", Prefix:"/", Delimiter:"/", Optional:false, Repeat:false, Pattern:"[^\\/]+?"},
85-
// {Name:"bar", Prefix:"/", Delimiter:"/", Optional:false, Repeat:false, Pattern:"[^\\/]+?"}
85+
// {Name:"foo", Prefix:"/", Suffix:"", Pattern:"[^\\/]+?", Modifier:""},
86+
// {Name:"bar", Prefix:"/", Suffix:"", Pattern:"[^\\/]+?", Modifier:""}
8687
// ]
8788

88-
match, err := regexp.FindStringMatch("/test/route")
89+
match, _ := regexp.FindStringMatch("/test/route")
8990
for _, g := range match.Groups() {
9091
fmt.Printf("%q ", g.String())
9192
}
92-
fmt.Printf("%d, %q\n", match.Index, match)
93-
//=> "/test/route" "test" "route" 0, "/test/route"
93+
fmt.Printf("%d %q\n", match.Index, match)
94+
//=> "/test/route" "test" "route" 0 "/test/route"
9495
```
9596

9697
**Please note:** Parameter names must use "word characters" (`[A-Za-z0-9_]`).
9798

98-
#### Parameter Modifiers
99+
##### Custom Matching Parameters
99100

100-
##### Optional
101-
102-
Parameters can be suffixed with a question mark (`?`) to make the parameter optional.
101+
Parameters can have a custom regexp, which overrides the default match (`[^/]+`). For example, you can match digits or names in a path:
103102

104103
```go
105-
regexp := pathToRegexp.Must(pathToRegexp.PathToRegexp("/:foo/:bar?", nil, nil))
106-
// tokens: [
107-
// {Name:"foo", Prefix:"/", Delimiter:"/", Optional:false, Repeat:false, Pattern:"[^\\/]+?"},
108-
// {Name:"bar", Prefix:"/", Delimiter:"/", Optional:true, Repeat:false, Pattern:"[^\\/]+?"}
109-
// ]
104+
var tokens []pathToRegexp.Token
105+
regexpNumbers := pathToRegexp.Must(pathToRegexp.PathToRegexp("/icon-:foo(\\d+).png", &tokens, nil))
106+
// tokens: [{Name:"foo", Prefix:"", Suffix:"", Pattern:"\\d+", Modifier:""}]
110107

111-
match, err := regexp.FindStringMatch("/test")
108+
match, _ := regexpNumbers.FindStringMatch("/icon-123.png")
112109
for _, g := range match.Groups() {
113110
fmt.Printf("%q ", g.String())
114111
}
115-
fmt.Printf("%d, %q\n", match.Index, match)
116-
//=> "/test" "test" "" 0, "/test"
112+
//=> "/icon-123.png" "123"
117113

118-
match, err = regexp.FindStringMatch("/test/route")
114+
match, _ := regexpNumbers.FindStringMatch("/icon-abc.png")
115+
fmt.Println(match)
116+
//=> <nil>
117+
118+
tokens = make([]pathToRegexp.Token, 0)
119+
regexpWord := pathToRegexp("/(user|u)", &tokens, nil)
120+
// tokens: [{Name:0, Prefix:"/", Suffix:"", Pattern:"user|u", Modifier:""}]
121+
122+
match, _ = regexpWord.FindStringMatch("/u")
119123
for _, g := range match.Groups() {
120124
fmt.Printf("%q ", g.String())
121125
}
122-
fmt.Printf("%d, %q\n", match.Index, match)
123-
//=> "/test/route" "test" "route" 0, "/test/route"
126+
//=> "/u" "u"
127+
128+
match, _ = regexpWord.FindStringMatch("users")
129+
fmt.Println(match)
130+
//=> <nil>
124131
```
125132

126-
**Tip:** The prefix is also optional, escape the prefix `\/` to make it required.
133+
**Tip:** Backslashes need to be escaped with another backslash in JavaScript strings.
127134

128-
##### Zero or more
135+
##### Custom Prefix and Suffix
129136

130-
Parameters can be suffixed with an asterisk (`*`) to denote a zero or more parameter matches. The prefix is used for each match.
137+
Parameters can be wrapped in `{}` to create custom prefixes or suffixes for your segment:
131138

132139
```go
133-
regexp := pathToRegexp.Must(pathToRegexp.PathToRegexp("/:foo*", nil, nil))
134-
// tokens: [{Name:"foo", Prefix:"/", Delimiter:"/", Optional:true, Repeat:true, Pattern:"[^\\/]+?"}]
140+
regexp := pathToRegexp.Must(pathToRegexp.PathToRegexp("/:attr1?{-:attr2}?{-:attr3}?", nil, nil))
135141

136-
match, err := regexp.FindStringMatch("/")
142+
match, _ := regexp.FindStringMatch("/test")
137143
for _, g := range match.Groups() {
138144
fmt.Printf("%q ", g.String())
139145
}
140-
fmt.Printf("%d, %q\n", match.Index, match)
141-
//=> "/" "" 0, "/"
146+
//=> "/test" "test" "" ""
142147

143-
match, err = regexp.FindStringMatch("/bar/baz")
148+
match, _ = regexp.FindStringMatch("/test-test")
144149
for _, g := range match.Groups() {
145150
fmt.Printf("%q ", g.String())
146151
}
147-
fmt.Printf("%d, %q\n", match.Index, match)
148-
//=> "/bar/baz" "bar/baz" 0, "/bar/baz"
152+
//=> "/test-test" "test" "test" ""
149153
```
150154

151-
##### One or more
155+
#### Unnamed Parameters
152156

153-
Parameters can be suffixed with a plus sign (`+`) to denote a one or more parameter matches. The prefix is used for each match.
157+
It is possible to write an unnamed parameter that only consists of a regexp. It works the same the named parameter, except it will be numerically indexed:
154158

155159
```go
156-
regexp := pathToRegexp.Must(pathToRegexp.PathToRegexp("/:foo+", nil, nil))
157-
// tokens: [{Name:"foo", Prefix:"/", Delimiter:"/", Optional:false, Repeat:true, Pattern:"[^\\/]+?"}]
158-
159-
match, err := regexp.FindStringMatch("/")
160-
fmt.Println(match)
161-
//=> nil
160+
var tokens []pathToRegexp.Token
161+
regexp := pathToRegexp.Must(pathToRegexp.PathToRegexp("/:foo/(.*)", &tokens, nil))
162+
// tokens: [
163+
// {Name:"foo", Prefix:"/", Suffix:"", Pattern:"[^\\/]+?", Modifier:""}
164+
// {Name:0, Prefix:"/", Suffix:"", Pattern:".*", Modifier:""}
165+
// ]
162166

163-
match, err = regexp.FindStringMatch("/bar/baz")
167+
match, _ := regexp.FindStringMatch("/test/route")
164168
for _, g := range match.Groups() {
165169
fmt.Printf("%q ", g.String())
166170
}
167-
fmt.Printf("%d, %q\n", match.Index, match)
168-
//=> "/bar/baz" "bar/baz" 0, "/bar/baz"
171+
fmt.Printf("%d %q\n", match.Index, match)
172+
//=> "/test/route" "test" "route" 0 "/test/route"
169173
```
170174

171-
#### Unnamed Parameters
175+
#### Modifiers
172176

173-
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.
177+
Modifiers must be placed after the parameter (e.g. `/:foo?`, `/(test)?`, or `/:foo(test)?`).
178+
179+
##### Optional
180+
181+
Parameters can be suffixed with a question mark (`?`) to make the parameter optional.
174182

175183
```go
176-
regexp := pathToRegexp.Must(pathToRegexp.PathToRegexp("/:foo/(.*)", nil, nil))
184+
regexp := pathToRegexp.Must(pathToRegexp.PathToRegexp("/:foo/:bar?", nil, nil))
177185
// tokens: [
178-
// {Name:"foo", Prefix:"/", Delimiter:"/", Optional:false, Repeat:false, Pattern:"[^\\/]+?"},
179-
// {Name:0, Prefix:"/", Delimiter:"/", Optional:false, Repeat:false, Pattern:".*"}
186+
// {Name:"foo", Prefix:"/", Suffix:"", Pattern:"[^\\/]+?", Modifier:""}
187+
// {Name:"bar", Prefix:"/", Suffix:"", Pattern:"[^\\/]+?", Modifier:"?"}
180188
// ]
181189

182-
match, err := regexp.FindStringMatch("/test/route")
190+
match, err := regexp.FindStringMatch("/test")
191+
for _, g := range match.Groups() {
192+
fmt.Printf("%q ", g.String())
193+
}
194+
fmt.Printf("%d %q\n", match.Index, match)
195+
//=> "/test" "test" "" 0 "/test"
196+
197+
match, err = regexp.FindStringMatch("/test/route")
183198
for _, g := range match.Groups() {
184199
fmt.Printf("%q ", g.String())
185200
}
186-
fmt.Printf("%d, %q\n", match.Index, match)
187-
//=> "/test/route" "test" "route" 0, "/test/route"
201+
fmt.Printf("%d %q\n", match.Index, match)
202+
//=> "/test/route" "test" "route" 0 "/test/route"
188203
```
189204

190-
#### Custom Matching Parameters
205+
**Tip:** The prefix is also optional, escape the prefix `\/` to make it required.
206+
207+
##### Zero or more
191208

192-
All parameters can have a custom regexp, which overrides the default match (`[^/]+`). For example, you can match digits or names in a path:
209+
Parameters can be suffixed with an asterisk (`*`) to denote a zero or more parameter matches.
193210

194211
```go
195-
regexpNumbers := pathToRegexp.Must(pathToRegexp.PathToRegexp("/icon-:foo(\\d+).png", nil, nil))
196-
// tokens: {Name:"foo", Prefix:"-", Delimiter:"-", Optional:false, Repeat:false, Pattern:"\\d+"}
212+
regexp := pathToRegexp.Must(pathToRegexp.PathToRegexp("/:foo*", nil, nil))
213+
// tokens: [{Name:"foo", Prefix:"/", Suffix:"", Pattern:"[^\\/]+?", Modifier:"*"}]
197214

198-
match, err := regexpNumbers.FindStringMatch("/icon-123.png")
215+
match, err := regexp.FindStringMatch("/")
199216
for _, g := range match.Groups() {
200217
fmt.Printf("%q ", g.String())
201218
}
202-
//=> "/icon-123.png" "123"
203-
204-
match, err = regexpNumbers.FindStringMatch("/icon-abc.png")
205-
fmt.Println(match)
206-
//=> nil
219+
fmt.Printf("%d %q\n", match.Index, match)
220+
//=> "/" "" 0 "/"
207221

208-
regexpWord := pathToRegexp.Must(pathToRegexp.PathToRegexp("/(user|u)", nil, nil))
209-
// tokens: {Name:0, Prefix:"/", Delimiter:"/", Optional:false, Repeat:false, Pattern:"user|u"}
210-
211-
match, err = regexpWord.FindStringMatch("/u")
222+
match, err = regexp.FindStringMatch("/bar/baz")
212223
for _, g := range match.Groups() {
213224
fmt.Printf("%q ", g.String())
214225
}
215-
//=> "/u" "u"
226+
fmt.Printf("%d %q\n", match.Index, match)
227+
//=> "/bar/baz" "bar/baz" 0 "/bar/baz"
228+
```
216229

217-
match, err = regexpWord.FindStringMatch("/users")
230+
##### One or more
231+
232+
Parameters can be suffixed with a plus sign (`+`) to denote a one or more parameter matches.
233+
234+
```go
235+
regexp := pathToRegexp.Must(pathToRegexp.PathToRegexp("/:foo+", nil, nil))
236+
// tokens: [{Name:"foo", Prefix:"/", Suffix:"", Pattern:"[^\\/]+?", Modifier:"+"}]
237+
238+
match, err := regexp.FindStringMatch("/")
218239
fmt.Println(match)
219240
//=> nil
220-
```
221241

222-
**Tip:** Backslashes need to be escaped with another backslash in Go strings.
242+
match, err = regexp.FindStringMatch("/bar/baz")
243+
for _, g := range match.Groups() {
244+
fmt.Printf("%q ", g.String())
245+
}
246+
fmt.Printf("%d %q\n", match.Index, match)
247+
//=> "/bar/baz" "bar/baz" 0 "/bar/baz"
248+
```
223249

224250
### Match
225251

@@ -250,10 +276,10 @@ fmt.Printf("%#v\n", tokens[0])
250276
//=> "/route"
251277

252278
fmt.Printf("%#v\n", tokens[1])
253-
//=> pathToRegexp.Token{Name:"foo", Prefix:"/", Delimiter:"/", Optional:false, Repeat:false, Pattern:"[^\\/]+?"}
279+
//=> pathToRegexp.Token{Name:"foo", Prefix:"/", Suffix:"", Pattern:"[^\\/]+?", Modifier:""}
254280

255281
fmt.Printf("%#v\n", tokens[2])
256-
//=> pathToRegexp.Token{Name:0, Prefix:"/", Delimiter:"/", Optional:false, Repeat:false, Pattern:".*"}
282+
//=> pathToRegexp.Token{Name:0, Prefix:"/", Suffix:"", Pattern:".*", Modifier:""}
257283
```
258284

259285
**Note:** This method only works with strings.
@@ -294,4 +320,4 @@ toPathRegexp = pathToRegexp.MustCompile("/user/:id(\\d+)", nil)
294320
toPathRegexp(map[string]string{"id": "abc"}) //=> panic
295321
```
296322

297-
**Note:** The generated function will panic on invalid input. It will do all necessary checks to ensure the generated path is valid. This method only works with strings.
323+
**Note:** The generated function will panic on invalid input.

0 commit comments

Comments
 (0)