mustermann19 0.3.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +18 -0
- data/.rspec +2 -0
- data/.travis.yml +10 -0
- data/.yardopts +1 -0
- data/Gemfile +2 -0
- data/LICENSE +22 -0
- data/README.md +1081 -0
- data/Rakefile +6 -0
- data/bench/capturing.rb +57 -0
- data/bench/regexp.rb +21 -0
- data/bench/simple_vs_sinatra.rb +23 -0
- data/bench/template_vs_addressable.rb +26 -0
- data/internals.md +64 -0
- data/lib/mustermann.rb +61 -0
- data/lib/mustermann/ast/compiler.rb +168 -0
- data/lib/mustermann/ast/expander.rb +134 -0
- data/lib/mustermann/ast/node.rb +160 -0
- data/lib/mustermann/ast/parser.rb +137 -0
- data/lib/mustermann/ast/pattern.rb +84 -0
- data/lib/mustermann/ast/transformer.rb +129 -0
- data/lib/mustermann/ast/translator.rb +108 -0
- data/lib/mustermann/ast/tree_renderer.rb +29 -0
- data/lib/mustermann/ast/validation.rb +43 -0
- data/lib/mustermann/caster.rb +117 -0
- data/lib/mustermann/equality_map.rb +48 -0
- data/lib/mustermann/error.rb +6 -0
- data/lib/mustermann/expander.rb +206 -0
- data/lib/mustermann/extension.rb +52 -0
- data/lib/mustermann/identity.rb +19 -0
- data/lib/mustermann/mapper.rb +98 -0
- data/lib/mustermann/pattern.rb +182 -0
- data/lib/mustermann/rails.rb +17 -0
- data/lib/mustermann/regexp_based.rb +30 -0
- data/lib/mustermann/regular.rb +26 -0
- data/lib/mustermann/router.rb +9 -0
- data/lib/mustermann/router/rack.rb +50 -0
- data/lib/mustermann/router/simple.rb +144 -0
- data/lib/mustermann/shell.rb +29 -0
- data/lib/mustermann/simple.rb +38 -0
- data/lib/mustermann/simple_match.rb +30 -0
- data/lib/mustermann/sinatra.rb +22 -0
- data/lib/mustermann/template.rb +48 -0
- data/lib/mustermann/to_pattern.rb +45 -0
- data/lib/mustermann/version.rb +3 -0
- data/mustermann.gemspec +31 -0
- data/spec/expander_spec.rb +105 -0
- data/spec/extension_spec.rb +296 -0
- data/spec/identity_spec.rb +83 -0
- data/spec/mapper_spec.rb +83 -0
- data/spec/mustermann_spec.rb +65 -0
- data/spec/pattern_spec.rb +49 -0
- data/spec/rails_spec.rb +522 -0
- data/spec/regexp_based_spec.rb +8 -0
- data/spec/regular_spec.rb +36 -0
- data/spec/router/rack_spec.rb +39 -0
- data/spec/router/simple_spec.rb +32 -0
- data/spec/shell_spec.rb +109 -0
- data/spec/simple_match_spec.rb +10 -0
- data/spec/simple_spec.rb +237 -0
- data/spec/sinatra_spec.rb +574 -0
- data/spec/support.rb +5 -0
- data/spec/support/coverage.rb +16 -0
- data/spec/support/env.rb +15 -0
- data/spec/support/expand_matcher.rb +27 -0
- data/spec/support/match_matcher.rb +39 -0
- data/spec/support/pattern.rb +39 -0
- data/spec/template_spec.rb +815 -0
- data/spec/to_pattern_spec.rb +20 -0
- metadata +301 -0
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/.yardopts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
- README.md internals.md
|
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Konstantin Haase
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person
|
4
|
+
obtaining a copy of this software and associated documentation
|
5
|
+
files (the "Software"), to deal in the Software without
|
6
|
+
restriction, including without limitation the rights to use,
|
7
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
copies of the Software, and to permit persons to whom the
|
9
|
+
Software is furnished to do so, subject to the following
|
10
|
+
conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be
|
13
|
+
included in all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
17
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
19
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
20
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
21
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
22
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,1081 @@
|
|
1
|
+
# The Amazing Mustermann
|
2
|
+
|
3
|
+
[![Build Status](https://travis-ci.org/namusyaka/mustermann.svg?branch=19support)](https://travis-ci.org/namusyaka/mustermann) [![Coverage Status](http://img.shields.io/coveralls/rkh/mustermann.svg?branch=master)](https://coveralls.io/r/rkh/mustermann) [![Code Climate](http://img.shields.io/codeclimate/github/rkh/mustermann.svg)](https://codeclimate.com/github/rkh/mustermann) [![Dependency Status](https://gemnasium.com/rkh/mustermann.svg)](https://gemnasium.com/rkh/mustermann) [![Gem Version](http://img.shields.io/gem/v/mustermann.svg)](https://rubygems.org/gems/mustermann)
|
4
|
+
[![Inline docs](http://inch-ci.org/github/rkh/mustermann.svg)](http://inch-ci.org/github/rkh/mustermann)
|
5
|
+
[![Documentation](http://img.shields.io/:yard-docs-38c800.svg)](http://rubydoc.info/gems/mustermann/frames)
|
6
|
+
[![License](http://img.shields.io/:license-MIT-38c800.svg)](http://rkh.mit-license.org)
|
7
|
+
[![Badges](http://img.shields.io/:badges-9/9-38c800.svg)](http://img.shields.io)
|
8
|
+
|
9
|
+
|
10
|
+
*Make sure you view the correct docs: [latest release](http://rubydoc.info/gems/mustermann/frames), [master](http://rubydoc.info/github/rkh/mustermann/master/frames).*
|
11
|
+
|
12
|
+
Welcome to [Mustermann](http://en.wikipedia.org/wiki/List_of_placeholder_names_by_language#German). Mustermann is your personal string matching expert. As an expert in the field of strings and patterns, Mustermann also has no runtime dependencies and is fully covered with specs and documentation.
|
13
|
+
|
14
|
+
Given a string pattern, Mustermann will turn it into an object that behaves like a regular expression and has comparable performance characteristics.
|
15
|
+
|
16
|
+
``` ruby
|
17
|
+
if '/foo/bar' =~ Mustermann.new('/foo/*')
|
18
|
+
puts 'it works!'
|
19
|
+
end
|
20
|
+
|
21
|
+
case 'something.png'
|
22
|
+
when Mustermann.new('foo/*') then puts "prefixed with foo"
|
23
|
+
when Mustermann.new('*.pdf') then puts "it's a PDF"
|
24
|
+
when Mustermann.new('*.png') then puts "it's an image"
|
25
|
+
end
|
26
|
+
```
|
27
|
+
|
28
|
+
Besides being a `Regexp` look-alike, Mustermann also adds a `params` method, that will give you a Sinatra-style hash:
|
29
|
+
|
30
|
+
``` ruby
|
31
|
+
pattern = Mustermann.new('/:prefix/*.*')
|
32
|
+
pattern.params('/a/b.c') # => { "prefix" => "a", splat => ["b", "c"] }
|
33
|
+
```
|
34
|
+
|
35
|
+
It's generally a good idea to reuse pattern objects, since as much computation as possible is happening during object creation, so that the actual matching or expanding is quite fast.
|
36
|
+
|
37
|
+
## Types and Options
|
38
|
+
|
39
|
+
You can pass in additional options to take fine grained control over the pattern:
|
40
|
+
|
41
|
+
``` ruby
|
42
|
+
Mustermann.new('/:foo.:bar', capture: :alpha) # :foo and :bar will only match alphabetic characters
|
43
|
+
```
|
44
|
+
|
45
|
+
In fact, you can even completely change the pattern type:
|
46
|
+
|
47
|
+
``` ruby
|
48
|
+
Mustermann.new('/**/*.png', type: :shell)
|
49
|
+
```
|
50
|
+
|
51
|
+
The available types are:
|
52
|
+
|
53
|
+
<table>
|
54
|
+
<thead>
|
55
|
+
<tr>
|
56
|
+
<th>Type</th>
|
57
|
+
<th>Description</th>
|
58
|
+
<th>Example</th>
|
59
|
+
<th>Available Options</th>
|
60
|
+
<th>Additional Features</th>
|
61
|
+
</tr>
|
62
|
+
</thead>
|
63
|
+
<tbody>
|
64
|
+
<tr>
|
65
|
+
<th><a href="#identity"><tt>identity</tt></a></th>
|
66
|
+
<td>URI unescaped input string has to match exactly</td>
|
67
|
+
<td><tt>/image.png</tt></td>
|
68
|
+
<td>
|
69
|
+
<a href="#ignore_unknown_options"><tt>ignore_unknown_options</tt></a>,
|
70
|
+
<a href="#uri_decode"><tt>uri_decode</tt></a>
|
71
|
+
</td>
|
72
|
+
<td></td>
|
73
|
+
</tr>
|
74
|
+
<tr>
|
75
|
+
<th><a href="#rails"><tt>rails</tt></a></th>
|
76
|
+
<td>Rails style patterns</td>
|
77
|
+
<td><tt>/:slug(.:ext)</tt></td>
|
78
|
+
<td>
|
79
|
+
<a href="#capture"><tt>capture</tt></a>,
|
80
|
+
<a href="#except"><tt>except</tt></a>,
|
81
|
+
<a href="#greedy"><tt>greedy</tt></a>,
|
82
|
+
<a href="#ignore_unknown_options"><tt>ignore_unknown_options</tt></a>,
|
83
|
+
<a href="#space_matches_plus"><tt>space_matches_plus</tt></a>,
|
84
|
+
<a href="#uri_decode"><tt>uri_decode</tt></a>
|
85
|
+
</td>
|
86
|
+
<td>
|
87
|
+
<a href="#pattern_expanding">Expanding</a>
|
88
|
+
</td>
|
89
|
+
</tr>
|
90
|
+
<tr>
|
91
|
+
<th><a href="#regexp"><tt>regexp</tt></a></th>
|
92
|
+
<td>Regular expressions as implemented by Ruby</td>
|
93
|
+
<td><tt>/(?<slug>.*)</tt></td>
|
94
|
+
<td>
|
95
|
+
<a href="#ignore_unknown_options"><tt>ignore_unknown_options</tt></a>,
|
96
|
+
<a href="#uri_decode"><tt>uri_decode</tt></a>
|
97
|
+
</td>
|
98
|
+
<td></td>
|
99
|
+
</tr>
|
100
|
+
<tr>
|
101
|
+
<th><a href="#shell"><tt>shell</tt></th>
|
102
|
+
<td>Unix style patterns</td>
|
103
|
+
<td><tt>/*.{png,jpg}</tt></td>
|
104
|
+
<td>
|
105
|
+
<a href="#ignore_unknown_options"><tt>ignore_unknown_options</tt></a>,
|
106
|
+
<a href="#uri_decode"><tt>uri_decode</tt></a>
|
107
|
+
</td>
|
108
|
+
<td></td>
|
109
|
+
</tr>
|
110
|
+
<tr>
|
111
|
+
<th><a href="#simple"><tt>simple</tt></a></th>
|
112
|
+
<td>Sinatra 1.3 style patterns</td>
|
113
|
+
<td><tt>/:slug.:ext</tt></td>
|
114
|
+
<td>
|
115
|
+
<a href="#greedy"><tt>greedy</tt></a>,
|
116
|
+
<a href="#ignore_unknown_options"><tt>ignore_unknown_options</tt></a>,
|
117
|
+
<a href="#space_matches_plus"><tt>space_matches_plus</tt></a>,
|
118
|
+
<a href="#uri_decode"><tt>uri_decode</tt></a>
|
119
|
+
</td>
|
120
|
+
<td></td>
|
121
|
+
</tr>
|
122
|
+
<tr>
|
123
|
+
<th><a href="#sinatra"><tt>sinatra</tt></a></th>
|
124
|
+
<td>Sinatra 2.0 style patterns (default)</td>
|
125
|
+
<td><tt>/:slug(.:ext)?</tt></td>
|
126
|
+
<td>
|
127
|
+
<a href="#capture"><tt>capture</tt></a>,
|
128
|
+
<a href="#except"><tt>except</tt></a>,
|
129
|
+
<a href="#greedy"><tt>greedy</tt></a>,
|
130
|
+
<a href="#ignore_unknown_options"><tt>ignore_unknown_options</tt></a>,
|
131
|
+
<a href="#space_matches_plus"><tt>space_matches_plus</tt></a>,
|
132
|
+
<a href="#uri_decode"><tt>uri_decode</tt></a>
|
133
|
+
</td>
|
134
|
+
<td>
|
135
|
+
<a href="#pattern_expanding">Expanding</a>
|
136
|
+
</td>
|
137
|
+
</tr>
|
138
|
+
<tr>
|
139
|
+
<th><a href="#template"><tt>template</tt></a></th>
|
140
|
+
<td><a href="http://tools.ietf.org/html/rfc6570">URI templates</a></td>
|
141
|
+
<td><tt>/dictionary/{term}</tt></td>
|
142
|
+
<td>
|
143
|
+
<a href="#capture"><tt>capture</tt></a>,
|
144
|
+
<a href="#except"><tt>except</tt></a>,
|
145
|
+
<a href="#greedy"><tt>greedy</tt></a>,
|
146
|
+
<a href="#ignore_unknown_options"><tt>ignore_unknown_options</tt></a>,
|
147
|
+
<a href="#space_matches_plus"><tt>space_matches_plus</tt></a>,
|
148
|
+
<a href="#uri_decode"><tt>uri_decode</tt></a>
|
149
|
+
</td>
|
150
|
+
<td>
|
151
|
+
<a href="#pattern_expanding">Expanding</a>
|
152
|
+
</td>
|
153
|
+
</tr>
|
154
|
+
</tbody>
|
155
|
+
</table>
|
156
|
+
|
157
|
+
See below for more details.
|
158
|
+
|
159
|
+
## Sinatra Integration
|
160
|
+
|
161
|
+
All patterns implement `match`, which means they can be dropped into Sinatra and other Rack routers:
|
162
|
+
|
163
|
+
``` ruby
|
164
|
+
require 'sinatra'
|
165
|
+
require 'mustermann'
|
166
|
+
|
167
|
+
get Mustermann.new('/:foo') do
|
168
|
+
params[:foo]
|
169
|
+
end
|
170
|
+
```
|
171
|
+
|
172
|
+
In fact, since using this with Sinatra is the main use case, it comes with a build-in extension for **Sinatra 1.x**.
|
173
|
+
|
174
|
+
``` ruby
|
175
|
+
require 'sinatra'
|
176
|
+
require 'mustermann'
|
177
|
+
|
178
|
+
register Mustermann
|
179
|
+
|
180
|
+
# this will use Mustermann rather than the built-in pattern matching
|
181
|
+
get '/:slug(.ext)?' do
|
182
|
+
params[:slug]
|
183
|
+
end
|
184
|
+
```
|
185
|
+
|
186
|
+
### Configuration
|
187
|
+
|
188
|
+
You can change what pattern type you want to use for your app via the `pattern` option:
|
189
|
+
|
190
|
+
``` ruby
|
191
|
+
require 'sinatra/base'
|
192
|
+
require 'mustermann'
|
193
|
+
|
194
|
+
class MyApp < Sinatra::Base
|
195
|
+
register Mustermann
|
196
|
+
set :pattern, type: :shell
|
197
|
+
|
198
|
+
get '/images/*.png' do
|
199
|
+
send_file request.path_info
|
200
|
+
end
|
201
|
+
|
202
|
+
get '/index{.htm,.html,}' do
|
203
|
+
erb :index
|
204
|
+
end
|
205
|
+
end
|
206
|
+
```
|
207
|
+
|
208
|
+
You can use the same setting for options:
|
209
|
+
|
210
|
+
``` ruby
|
211
|
+
require 'sinatra'
|
212
|
+
require 'mustermann'
|
213
|
+
|
214
|
+
register Mustermann
|
215
|
+
set :pattern, capture: { ext: %w[png jpg html txt] }
|
216
|
+
|
217
|
+
get '/:slug(.:ext)?' do
|
218
|
+
# slug will be 'foo' for '/foo.png'
|
219
|
+
# slug will be 'foo.bar' for '/foo.bar'
|
220
|
+
# slug will be 'foo.bar' for '/foo.bar.html'
|
221
|
+
params[:slug]
|
222
|
+
end
|
223
|
+
```
|
224
|
+
|
225
|
+
It is also possible to pass in options to a specific route:
|
226
|
+
|
227
|
+
``` ruby
|
228
|
+
require 'sinatra'
|
229
|
+
require 'mustermann'
|
230
|
+
|
231
|
+
register Mustermann
|
232
|
+
|
233
|
+
get '/:slug(.:ext)?', pattern: { greedy: false } do
|
234
|
+
# slug will be 'foo' for '/foo.png'
|
235
|
+
# slug will be 'foo' for '/foo.bar'
|
236
|
+
# slug will be 'foo' for '/foo.bar.html'
|
237
|
+
params[:slug]
|
238
|
+
end
|
239
|
+
```
|
240
|
+
|
241
|
+
Of course, all of the above can be combined.
|
242
|
+
Moreover, the `capture` and the `except` option can be passed to route directly.
|
243
|
+
And yes, this also works with `before` and `after` filters.
|
244
|
+
|
245
|
+
``` ruby
|
246
|
+
require 'sinatra/base'
|
247
|
+
require 'sinatra/respond_with'
|
248
|
+
require 'mustermann'
|
249
|
+
|
250
|
+
class MyApp < Sinatra::Base
|
251
|
+
register Mustermann, Sinatra::RespondWith
|
252
|
+
set :pattern, capture: { id: /\d+/ } # id will only match digits
|
253
|
+
|
254
|
+
# only capture extensions known to Rack
|
255
|
+
before '*:ext', capture: Rack::Mime::MIME_TYPES.keys do
|
256
|
+
content_type params[:ext] # set Content-Type
|
257
|
+
request.path_info = params[:splat].first # drop the extension
|
258
|
+
end
|
259
|
+
|
260
|
+
get '/:id' do
|
261
|
+
not_found unless page = Page.find params[:id]
|
262
|
+
respond_with(page)
|
263
|
+
end
|
264
|
+
end
|
265
|
+
```
|
266
|
+
|
267
|
+
### Why would I want this?
|
268
|
+
|
269
|
+
* It gives you fine grained control over the pattern matching
|
270
|
+
* Allows you to use different pattern styles in your app
|
271
|
+
* The default is more robust and powerful than the built-in patterns
|
272
|
+
* Sinatra 2.0 will use Mustermann internally
|
273
|
+
* Better exceptions for broken route syntax
|
274
|
+
|
275
|
+
### Why not include this in Sinatra 1.x?
|
276
|
+
|
277
|
+
* It would introduce breaking changes, even though these would be minor
|
278
|
+
* Like Sinatra 2.0, Mustermann requires Ruby 2.0 or newer
|
279
|
+
|
280
|
+
<a name="pattern_expanding"></a>
|
281
|
+
## Expanding
|
282
|
+
|
283
|
+
Similarly to parsing, it is also possible to generate a string from a pattern by expanding it with a hash.
|
284
|
+
For simple expansions, you can use `Pattern#expand`.
|
285
|
+
|
286
|
+
``` ruby
|
287
|
+
pattern = Mustermann.new('/:file(.:ext)?')
|
288
|
+
pattern.expand(file: 'pony') # => "/pony"
|
289
|
+
pattern.expand(file: 'pony', ext: 'jpg') # => "/pony.jpg"
|
290
|
+
pattern.expand(ext: 'jpg') # raises Mustermann::ExpandError
|
291
|
+
```
|
292
|
+
|
293
|
+
Expanding can be useful for instance when implementing link helpers.
|
294
|
+
|
295
|
+
### Expander Objects
|
296
|
+
|
297
|
+
To get fine-grained control over expansion, you can use `Mustermann::Expander` directly.
|
298
|
+
|
299
|
+
You can create an expander object directly from a string:
|
300
|
+
|
301
|
+
``` ruby
|
302
|
+
require 'mustermann/expander'
|
303
|
+
expander = Mustermann::Expander("/:file.jpg")
|
304
|
+
expander.expand(file: 'pony') # => "/pony.jpg"
|
305
|
+
|
306
|
+
expander = Mustermann::Expander(":file(.:ext)", type: :rails)
|
307
|
+
expander.expand(file: 'pony', ext: 'jpg') # => "/pony.jpg"
|
308
|
+
```
|
309
|
+
|
310
|
+
Or you can pass it a pattern instance:
|
311
|
+
|
312
|
+
``` ruby
|
313
|
+
require 'mustermann'
|
314
|
+
pattern = Mustermann.new("/:file")
|
315
|
+
|
316
|
+
require 'mustermann/expander'
|
317
|
+
expander = Mustermann::Expander.new(pattern)
|
318
|
+
```
|
319
|
+
|
320
|
+
### Expanding Multiple Patterns
|
321
|
+
|
322
|
+
You can add patterns to an expander object via `<<`:
|
323
|
+
|
324
|
+
``` ruby
|
325
|
+
expander = Mustermann::Expander.new
|
326
|
+
expander << "/users/:user_id"
|
327
|
+
expander << "/pages/:page_id"
|
328
|
+
|
329
|
+
expander.expand(user_id: 15) # => "/users/15"
|
330
|
+
expander.expand(page_id: 58) # => "/pages/58"
|
331
|
+
```
|
332
|
+
|
333
|
+
You can set pattern options when creating the expander:
|
334
|
+
|
335
|
+
``` ruby
|
336
|
+
expander = Mustermann::Expander.new(type: :template)
|
337
|
+
expander << "/users/{user_id}"
|
338
|
+
expander << "/pages/{page_id}"
|
339
|
+
```
|
340
|
+
|
341
|
+
Additionally, it is possible to combine patterns of different types:
|
342
|
+
|
343
|
+
``` ruby
|
344
|
+
expander = Mustermann::Expander.new
|
345
|
+
expander << Mustermann.new("/users/{user_id}", type: :template)
|
346
|
+
expander << Mustermann.new("/pages/:page_id", type: :rails)
|
347
|
+
```
|
348
|
+
|
349
|
+
### Handling Additional Values
|
350
|
+
|
351
|
+
The handling of additional values passed in to `expand` can be changed by setting the `additional_values` option:
|
352
|
+
|
353
|
+
``` ruby
|
354
|
+
expander = Mustermann::Expander.new("/:slug", additional_values: :raise)
|
355
|
+
expander.expand(slug: "foo", value: "bar") # raises Mustermann::ExpandError
|
356
|
+
|
357
|
+
expander = Mustermann::Expander.new("/:slug", additional_values: :ignore)
|
358
|
+
expander.expand(slug: "foo", value: "bar") # => "/foo"
|
359
|
+
|
360
|
+
expander = Mustermann::Expander.new("/:slug", additional_values: :append)
|
361
|
+
expander.expand(slug: "foo", value: "bar") # => "/foo?value=bar"
|
362
|
+
```
|
363
|
+
|
364
|
+
## Duck Typing
|
365
|
+
|
366
|
+
All methods converting string input to pattern objects will also accept any arbitrary object that implements `to_pattern`:
|
367
|
+
|
368
|
+
``` ruby
|
369
|
+
require 'mustermann'
|
370
|
+
|
371
|
+
class MyObject
|
372
|
+
def to_pattern(**options)
|
373
|
+
Mustermann.new("/foo", **options)
|
374
|
+
end
|
375
|
+
end
|
376
|
+
|
377
|
+
object = MyObject.new
|
378
|
+
Mustermann.new(object, type: :rails) # => #<Mustermann::Rails:"/foo">
|
379
|
+
```
|
380
|
+
|
381
|
+
It might also be that you want to call `to_pattern` yourself instead of `Mustermann.new`. You can load `mustermann/to_pattern` to implement this method for strings, regular expressions and pattern objects:
|
382
|
+
|
383
|
+
``` ruby
|
384
|
+
require 'mustermann/to_pattern'
|
385
|
+
|
386
|
+
"/foo".to_pattern # => #<Mustermann::Sinatra:"/foo">
|
387
|
+
"/foo".to_pattern(type: :rails) # => #<Mustermann::Rails:"/foo">
|
388
|
+
%r{/foo}.to_pattern # => #<Mustermann::Regular:"\\/foo">
|
389
|
+
"/foo".to_pattern.to_pattern # => #<Mustermann::Sinatra:"/foo">
|
390
|
+
```
|
391
|
+
|
392
|
+
You can also use the `Mustermann::ToPattern` mixin to easily add `to_pattern` to your own objects:
|
393
|
+
|
394
|
+
``` ruby
|
395
|
+
require 'mustermann/to_pattern'
|
396
|
+
|
397
|
+
class MyObject
|
398
|
+
include Mustermann::ToPattern
|
399
|
+
|
400
|
+
def to_s
|
401
|
+
"/foo"
|
402
|
+
end
|
403
|
+
end
|
404
|
+
|
405
|
+
MyObject.new.to_pattern # => #<Mustermann::Sinatra:"/foo">
|
406
|
+
```
|
407
|
+
|
408
|
+
## Partial Loading and Thread Safety
|
409
|
+
|
410
|
+
Pattern objects are generally assumed to be thread-safe. You can easily match strings against the same pattern object concurrently.
|
411
|
+
|
412
|
+
Mustermann will only load the pattern implementation you need. For example, `mustermann/rails` is loaded the first time you invoke `Mustermann.new(..., type: :rails)`. This part might not be thread-safe, depending on your Ruby implementation.
|
413
|
+
|
414
|
+
In the common use cases, that is Sinatra and similar, patterns are compiled on the main thread during the application load phase, so this is a non-issue there.
|
415
|
+
|
416
|
+
To avoid this, you can load the pattern types you need manually:
|
417
|
+
|
418
|
+
``` ruby
|
419
|
+
require 'mustermann/sinatra'
|
420
|
+
Mustermann::Sinatra.new('/:foo')
|
421
|
+
```
|
422
|
+
|
423
|
+
## Options
|
424
|
+
|
425
|
+
### `capture`
|
426
|
+
|
427
|
+
Supported by: `rails`, `sinatra`, `template`
|
428
|
+
|
429
|
+
**Sinatra**, **URI template** and **Rails** patterns support changing the way named captures work via the `capture` options.
|
430
|
+
|
431
|
+
Possible values for a capture:
|
432
|
+
|
433
|
+
``` ruby
|
434
|
+
# String: Matches the given string (or any URI encoded version of it)
|
435
|
+
Mustermann.new('/index.:ext', capture: 'png')
|
436
|
+
|
437
|
+
# Regexp: Matches the Regular expression
|
438
|
+
Mustermann.new('/:id', capture: /\d+/)
|
439
|
+
|
440
|
+
# Symbol: Matches POSIX character class
|
441
|
+
Mustermann.new('/:id', capture: :digit)
|
442
|
+
|
443
|
+
# Array of the above: Matches anything in the array
|
444
|
+
Mustermann.new('/:id_or_slug', capture: [/\d+/, :word])
|
445
|
+
|
446
|
+
# Hash of the above: Looks up the hash entry by capture name and uses value for matching
|
447
|
+
Mustermann.new('/:id.:ext', capture: { id: /\d+/, ext: ['png', 'jpg'] })
|
448
|
+
```
|
449
|
+
|
450
|
+
Available POSIX character classes are: `:alnum`, `:alpha`, `:blank`, `:cntrl`, `:digit`, `:graph`, `:lower`, `:print`, `:punct`, `:space`, `:upper`, `:xdigit`, `:word` and `:ascii`.
|
451
|
+
|
452
|
+
### `except`
|
453
|
+
|
454
|
+
Supported by: `rails`, `sinatra`, `template`
|
455
|
+
|
456
|
+
Given you supply a second pattern via the except option. Any string that would match the primary pattern but also matches the except pattern will not result in a successful match. Feel free to read that again. Or just take a look at this example:
|
457
|
+
|
458
|
+
``` ruby
|
459
|
+
pattern = Mustermann.new('/auth/*', except: '/auth/login')
|
460
|
+
pattern === '/auth/dunno' # => true
|
461
|
+
pattern === '/auth/login' # => false
|
462
|
+
```
|
463
|
+
|
464
|
+
Now, as said above, `except` treats the value as a pattern:
|
465
|
+
|
466
|
+
``` ruby
|
467
|
+
pattern = Mustermann.new('/*anything', type: :rails, except: '/*anything.png')
|
468
|
+
pattern === '/foo.jpg' # => true
|
469
|
+
pattern === '/foo.png' # => false
|
470
|
+
```
|
471
|
+
|
472
|
+
### `greedy`
|
473
|
+
|
474
|
+
Supported by: `rails`, `simple`, `sinatra`, `template`. Default value: `true`
|
475
|
+
|
476
|
+
**Simple** patterns are greedy, meaning that for the pattern `:foo:bar?`, everything will be captured as `foo`, `bar` will always be `nil`. By setting `greedy` to `false`, `foo` will capture as little as possible (which in this case would only be the first letter), leaving the rest to `bar`.
|
477
|
+
|
478
|
+
**Sinatra**, **URI template** and **Rails** patterns are semi-greedy. This means `:foo(.:bar)?` (`:foo(.:bar)` for Rails patterns) will capture everything before the *last* dot as `foo`. For these two pattern types, you can switch into non-greedy mode by setting the `greedy` option to false. In that case `foo` will only capture the part before the *first* dot.
|
479
|
+
|
480
|
+
Semi-greedy behavior is not specific to dots, it works with all characters or strings. For instance, `:a(foo:b)` will capture everything before the *last* `foo` as `a`, and `:foo(bar)?` will not capture a `bar` at the end.
|
481
|
+
|
482
|
+
``` ruby
|
483
|
+
pattern = Mustermann.new(':a.:b', greedy: true)
|
484
|
+
pattern.match('a.b.c.d') # => #<MatchData a:"a.b.c" b:"d">
|
485
|
+
|
486
|
+
pattern = Mustermann.new(':a.:b', greedy: false)
|
487
|
+
pattern.match('a.b.c.d') # => #<MatchData a:"a" b:"b.c.d">
|
488
|
+
```
|
489
|
+
|
490
|
+
### `space_matches_plus`
|
491
|
+
|
492
|
+
Supported by: `rails`, `simple`, `sinatra`, `template`. Default value: `true`
|
493
|
+
|
494
|
+
**Sinatra**, **Simple**, **URI template** and **Rails** patterns will by default also match a plus sign for a space in the pattern:
|
495
|
+
|
496
|
+
``` ruby
|
497
|
+
Mustermann.new('a b') === 'a+b' # => true
|
498
|
+
```
|
499
|
+
|
500
|
+
You can disable this behavior via `space_matches_plus`:
|
501
|
+
|
502
|
+
``` ruby
|
503
|
+
Mustermann.new('a b', space_matches_plus: false) === 'a+b' # => false
|
504
|
+
```
|
505
|
+
|
506
|
+
**Important:** This setting has no effect on captures, captures will always keep plus signs as plus sings and spaces as spaces:
|
507
|
+
|
508
|
+
``` ruby
|
509
|
+
pattern = Mustermann.new(':x')
|
510
|
+
pattern.match('a b')[:x] # => 'a b'
|
511
|
+
pattern.match('a+b')[:x] # => 'a+b'
|
512
|
+
````
|
513
|
+
|
514
|
+
### `uri_decode`
|
515
|
+
|
516
|
+
Supported by all patterns. Default value: `true`
|
517
|
+
|
518
|
+
Usually, characters in the pattern will also match the URI encoded version of these characters:
|
519
|
+
|
520
|
+
``` ruby
|
521
|
+
Mustermann.new('a b') === 'a b' # => true
|
522
|
+
Mustermann.new('a b') === 'a%20b' # => true
|
523
|
+
```
|
524
|
+
|
525
|
+
You can avoid this by setting `uri_decode` to `false`:
|
526
|
+
|
527
|
+
``` ruby
|
528
|
+
Mustermann.new('a b', uri_decode: false) === 'a b' # => true
|
529
|
+
Mustermann.new('a b', uri_decode: false) === 'a%20b' # => false
|
530
|
+
```
|
531
|
+
|
532
|
+
### `ignore_unknown_options`
|
533
|
+
|
534
|
+
Supported by all patterns. Default value: `false`
|
535
|
+
|
536
|
+
If you pass an option in that is not supported by the specific pattern type, Mustermann will raise an `ArgumentError`.
|
537
|
+
By setting `ignore_unknown_options` to `true`, it will happily ignore the option.
|
538
|
+
|
539
|
+
## Pattern Types
|
540
|
+
|
541
|
+
### `identity`
|
542
|
+
|
543
|
+
Identity patterns are strings that have to match the input exactly.
|
544
|
+
|
545
|
+
``` ruby
|
546
|
+
require 'mustermann'
|
547
|
+
|
548
|
+
pattern = Mustermann.new('/:example', type: :identity)
|
549
|
+
pattern === "/foo.bar" # => false
|
550
|
+
pattern === "/:example" # => true
|
551
|
+
pattern.params("/foo.bar") # => nil
|
552
|
+
pattern.params("/:example") # => {}
|
553
|
+
```
|
554
|
+
|
555
|
+
<table>
|
556
|
+
<thead>
|
557
|
+
<tr>
|
558
|
+
<th>Syntax Element</th>
|
559
|
+
<th>Description</th>
|
560
|
+
</tr>
|
561
|
+
</thead>
|
562
|
+
<tbody>
|
563
|
+
<tr>
|
564
|
+
<td><i>any character</i></td>
|
565
|
+
<td>Matches exactly that character or a URI escaped version of it.</td>
|
566
|
+
</tr>
|
567
|
+
</tbody>
|
568
|
+
</table>
|
569
|
+
|
570
|
+
### `rails`
|
571
|
+
|
572
|
+
Patterns with the syntax used in Rails route definitions.
|
573
|
+
|
574
|
+
``` ruby
|
575
|
+
require 'mustermann'
|
576
|
+
|
577
|
+
pattern = Mustermann.new('/:example', type: :rails)
|
578
|
+
pattern === "/foo.bar" # => true
|
579
|
+
pattern === "/foo/bar" # => false
|
580
|
+
pattern.params("/foo.bar") # => { "example" => "foo.bar" }
|
581
|
+
pattern.params("/foo/bar") # => nil
|
582
|
+
|
583
|
+
pattern = Mustermann.new('/:example(/:optional)', type: :rails)
|
584
|
+
pattern === "/foo.bar" # => true
|
585
|
+
pattern === "/foo/bar" # => true
|
586
|
+
pattern.params("/foo.bar") # => { "example" => "foo.bar", "optional" => nil }
|
587
|
+
pattern.params("/foo/bar") # => { "example" => "foo", "optional" => "bar" }
|
588
|
+
|
589
|
+
pattern = Mustermann.new('/*example', type: :rails)
|
590
|
+
pattern === "/foo.bar" # => true
|
591
|
+
pattern === "/foo/bar" # => true
|
592
|
+
pattern.params("/foo.bar") # => { "example" => "foo.bar" }
|
593
|
+
pattern.params("/foo/bar") # => { "example" => "foo/bar" }
|
594
|
+
```
|
595
|
+
|
596
|
+
<table>
|
597
|
+
<thead>
|
598
|
+
<tr>
|
599
|
+
<th>Syntax Element</th>
|
600
|
+
<th>Description</th>
|
601
|
+
</tr>
|
602
|
+
</thead>
|
603
|
+
<tbody>
|
604
|
+
<tr>
|
605
|
+
<td><b>:</b><i>name</i></td>
|
606
|
+
<td>
|
607
|
+
Captures anything but a forward slash in a semi-greedy fashion. Capture is named <i>name</i>.
|
608
|
+
Capture behavior can be modified with <a href="#capture"><tt>capture</tt></a> and <a href="#greedy"><tt>greedy</tt></a> option.
|
609
|
+
</td>
|
610
|
+
</tr>
|
611
|
+
<tr>
|
612
|
+
<td><b>*</b><i>name</i></td>
|
613
|
+
<td>
|
614
|
+
Captures anything in a non-greedy fashion. Capture is named <i>name</i>.
|
615
|
+
</td>
|
616
|
+
</tr>
|
617
|
+
<tr>
|
618
|
+
<td><b>(</b><i>expression</i><b>)</b></td>
|
619
|
+
<td>Enclosed <i>expression</i> is optional.</td>
|
620
|
+
</tr>
|
621
|
+
<tr>
|
622
|
+
<td><b>/</b></td>
|
623
|
+
<td>
|
624
|
+
Matches forward slash. Does not match URI encoded version of forward slash.
|
625
|
+
</td>
|
626
|
+
</tr>
|
627
|
+
<tr>
|
628
|
+
<td><i>any other character</i></td>
|
629
|
+
<td>Matches exactly that character or a URI encoded version of it.</td>
|
630
|
+
</tr>
|
631
|
+
</tbody>
|
632
|
+
</table>
|
633
|
+
|
634
|
+
### `regexp`
|
635
|
+
|
636
|
+
Regular expression patterns, as used implemented by Ruby. Do not include characters for matching beginning or end of string/line.
|
637
|
+
This pattern type is also known as `regular` and the pattern class is `Mustermann::Regular` (located in `mustermann/regular`).
|
638
|
+
|
639
|
+
``` ruby
|
640
|
+
require 'mustermann'
|
641
|
+
|
642
|
+
pattern = Mustermann.new('/(?<example>.*)', type: :regexp)
|
643
|
+
pattern === "/foo.bar" # => true
|
644
|
+
pattern === "/foo/bar" # => true
|
645
|
+
pattern.params("/foo.bar") # => { "example" => "foo.bar" }
|
646
|
+
pattern.params("/foo/bar") # => { "example" => "foo/bar" }
|
647
|
+
```
|
648
|
+
|
649
|
+
<table>
|
650
|
+
<thead>
|
651
|
+
<tr>
|
652
|
+
<th>Syntax Element</th>
|
653
|
+
<th>Description</th>
|
654
|
+
</tr>
|
655
|
+
</thead>
|
656
|
+
<tbody>
|
657
|
+
<tr>
|
658
|
+
<td><i>any string</i></td>
|
659
|
+
<td>Interpreted as regular expression.</td>
|
660
|
+
</tr>
|
661
|
+
</tbody>
|
662
|
+
</table>
|
663
|
+
|
664
|
+
It is also possible to turn a proper Regexp instance into a pattern object by passing it to `Mustermann.new`:
|
665
|
+
|
666
|
+
``` ruby
|
667
|
+
require 'mustermann'
|
668
|
+
Mustermann.new(/(?<example>.*)/).params("input") # => { "example" => "input" }
|
669
|
+
```
|
670
|
+
|
671
|
+
### `shell`
|
672
|
+
|
673
|
+
Shell patterns, as used in Bash or with `Dir.glob`.
|
674
|
+
|
675
|
+
``` ruby
|
676
|
+
require 'mustermann'
|
677
|
+
|
678
|
+
pattern = Mustermann.new('/*', type: :shell)
|
679
|
+
pattern === "/foo.bar" # => true
|
680
|
+
pattern === "/foo/bar" # => false
|
681
|
+
|
682
|
+
pattern = Mustermann.new('/**/*', type: :shell)
|
683
|
+
pattern === "/foo.bar" # => true
|
684
|
+
pattern === "/foo/bar" # => true
|
685
|
+
|
686
|
+
pattern = Mustermann.new('/{foo,bar}', type: :shell)
|
687
|
+
pattern === "/foo" # => true
|
688
|
+
pattern === "/bar" # => true
|
689
|
+
pattern === "/baz" # => false
|
690
|
+
```
|
691
|
+
|
692
|
+
<table>
|
693
|
+
<thead>
|
694
|
+
<tr>
|
695
|
+
<th>Syntax Element</th>
|
696
|
+
<th>Description</th>
|
697
|
+
</tr>
|
698
|
+
</thead>
|
699
|
+
<tbody>
|
700
|
+
<tr>
|
701
|
+
<td><b>*</b></td>
|
702
|
+
<td>Matches anything but a slash.</td>
|
703
|
+
</tr>
|
704
|
+
<tr>
|
705
|
+
<td><b>**</b></td>
|
706
|
+
<td>Matches anything.</td>
|
707
|
+
</tr>
|
708
|
+
<tr>
|
709
|
+
<td><b>[</b><i>set</i><b>]</b></td>
|
710
|
+
<td>Matches one character in <i>set</i>.</td>
|
711
|
+
</tr>
|
712
|
+
<tr>
|
713
|
+
<td><b>{</b><i>a</i>,<i>b</i><b>}</b></td>
|
714
|
+
<td>Matches <i>a</i> or <i>b</i>.</td>
|
715
|
+
</tr>
|
716
|
+
<tr>
|
717
|
+
<td><b>\</b><i>x</i></td>
|
718
|
+
<td>Matches <i>x</i> or URI encoded version of <i>x</i>. For instance <tt>\*</tt> matches <tt>*</tt>.</td>
|
719
|
+
</tr>
|
720
|
+
<tr>
|
721
|
+
<td><i>any other character</i></td>
|
722
|
+
<td>Matches exactly that character or a URI encoded version of it.</td>
|
723
|
+
</tr>
|
724
|
+
</tbody>
|
725
|
+
</table>
|
726
|
+
|
727
|
+
### `simple`
|
728
|
+
|
729
|
+
Patterns as used by Sinatra 1.3. Useful for porting an application that relies on this behavior to a later Sinatra version and to make sure [Sinatra 2.0](#sinatra) patterns do not decrease performance. Simple patterns internally use the same code older Sinatra versions used for compiling the pattern. Error messages for broken patterns will therefore not be as informative as for other pattern implementations.
|
730
|
+
|
731
|
+
``` ruby
|
732
|
+
require 'mustermann'
|
733
|
+
|
734
|
+
pattern = Mustermann.new('/:example', type: :simple)
|
735
|
+
pattern === "/foo.bar" # => true
|
736
|
+
pattern === "/foo/bar" # => false
|
737
|
+
pattern.params("/foo.bar") # => { "example" => "foo.bar" }
|
738
|
+
pattern.params("/foo/bar") # => nil
|
739
|
+
|
740
|
+
pattern = Mustermann.new('/:example/?:optional?', type: :simple)
|
741
|
+
pattern === "/foo.bar" # => true
|
742
|
+
pattern === "/foo/bar" # => true
|
743
|
+
pattern.params("/foo.bar") # => { "example" => "foo.bar", "optional" => nil }
|
744
|
+
pattern.params("/foo/bar") # => { "example" => "foo", "optional" => "bar" }
|
745
|
+
|
746
|
+
pattern = Mustermann.new('/*', type: :simple)
|
747
|
+
pattern === "/foo.bar" # => true
|
748
|
+
pattern === "/foo/bar" # => true
|
749
|
+
pattern.params("/foo.bar") # => { "splat" => ["foo.bar"] }
|
750
|
+
pattern.params("/foo/bar") # => { "splat" => ["foo/bar"] }
|
751
|
+
```
|
752
|
+
|
753
|
+
<table>
|
754
|
+
<thead>
|
755
|
+
<tr>
|
756
|
+
<th>Syntax Element</th>
|
757
|
+
<th>Description</th>
|
758
|
+
</tr>
|
759
|
+
</thead>
|
760
|
+
<tbody>
|
761
|
+
<tr>
|
762
|
+
<td><b>:</b><i>name</i></td>
|
763
|
+
<td>
|
764
|
+
Captures anything but a forward slash in a greedy fashion. Capture is named <i>name</i>.
|
765
|
+
</td>
|
766
|
+
</tr>
|
767
|
+
<tr>
|
768
|
+
<td><b>*</b></td>
|
769
|
+
<td>
|
770
|
+
Captures anything in a non-greedy fashion. Capture is named splat.
|
771
|
+
It is always an array of captures, as you can use <tt>*</tt> more than once in a pattern.
|
772
|
+
</td>
|
773
|
+
</tr>
|
774
|
+
<tr>
|
775
|
+
<td><i>x</i><b>?</b></td>
|
776
|
+
<td>Makes <i>x</i> optional. For instance <tt>foo?</tt> matches <tt>foo</tt> or <tt>fo</tt>.</td>
|
777
|
+
</tr>
|
778
|
+
<tr>
|
779
|
+
<td><b>/</b></td>
|
780
|
+
<td>
|
781
|
+
Matches forward slash. Does not match URI encoded version of forward slash.
|
782
|
+
</td>
|
783
|
+
</tr>
|
784
|
+
<tr>
|
785
|
+
<td><i>any special character</i></td>
|
786
|
+
<td>Matches exactly that character or a URI encoded version of it.</td>
|
787
|
+
</tr>
|
788
|
+
<tr>
|
789
|
+
<td><i>any other character</i></td>
|
790
|
+
<td>Matches exactly that character.</td>
|
791
|
+
</tr>
|
792
|
+
</tbody>
|
793
|
+
</table>
|
794
|
+
|
795
|
+
### `sinatra`
|
796
|
+
|
797
|
+
Sinatra 2.0 style patterns. The default used by Mustermann.
|
798
|
+
|
799
|
+
``` ruby
|
800
|
+
require 'mustermann'
|
801
|
+
|
802
|
+
pattern = Mustermann.new('/:example')
|
803
|
+
pattern === "/foo.bar" # => true
|
804
|
+
pattern === "/foo/bar" # => false
|
805
|
+
pattern.params("/foo.bar") # => { "example" => "foo.bar" }
|
806
|
+
pattern.params("/foo/bar") # => nil
|
807
|
+
|
808
|
+
pattern = Mustermann.new('/\:example')
|
809
|
+
pattern === "/foo.bar" # => false
|
810
|
+
pattern === "/:example" # => true
|
811
|
+
pattern.params("/foo.bar") # => nil
|
812
|
+
pattern.params("/:example") # => {}
|
813
|
+
|
814
|
+
pattern = Mustermann.new('/:example(/:optional)?')
|
815
|
+
pattern === "/foo.bar" # => true
|
816
|
+
pattern === "/foo/bar" # => true
|
817
|
+
pattern.params("/foo.bar") # => { "example" => "foo.bar", "optional" => nil }
|
818
|
+
pattern.params("/foo/bar") # => { "example" => "foo", "optional" => "bar" }
|
819
|
+
|
820
|
+
pattern = Mustermann.new('/*')
|
821
|
+
pattern === "/foo.bar" # => true
|
822
|
+
pattern === "/foo/bar" # => true
|
823
|
+
pattern.params("/foo.bar") # => { "splat" => ["foo.bar"] }
|
824
|
+
pattern.params("/foo/bar") # => { "splat" => ["foo/bar"] }
|
825
|
+
|
826
|
+
pattern = Mustermann.new('/*example')
|
827
|
+
pattern === "/foo.bar" # => true
|
828
|
+
pattern === "/foo/bar" # => true
|
829
|
+
pattern.params("/foo.bar") # => { "example" => "foo.bar" }
|
830
|
+
pattern.params("/foo/bar") # => { "example" => "foo/bar" }
|
831
|
+
```
|
832
|
+
|
833
|
+
<table>
|
834
|
+
<thead>
|
835
|
+
<tr>
|
836
|
+
<th>Syntax Element</th>
|
837
|
+
<th>Description</th>
|
838
|
+
</tr>
|
839
|
+
</thead>
|
840
|
+
<tbody>
|
841
|
+
<tr>
|
842
|
+
<td><b>:</b><i>name</i></td>
|
843
|
+
<td>
|
844
|
+
Captures anything but a forward slash in a semi-greedy fashion. Capture is named <i>name</i>.
|
845
|
+
Capture behavior can be modified with <a href="#capture"><tt>capture</tt></a> and <a href="#greedy"><tt>greedy</tt></a> option.
|
846
|
+
</td>
|
847
|
+
</tr>
|
848
|
+
<tr>
|
849
|
+
<td><b>*</b><i>name</i></td>
|
850
|
+
<td>
|
851
|
+
Captures anything in a non-greedy fashion. Capture is named <i>name</i>.
|
852
|
+
</td>
|
853
|
+
</tr>
|
854
|
+
<tr>
|
855
|
+
<td><b>*</b></td>
|
856
|
+
<td>
|
857
|
+
Captures anything in a non-greedy fashion. Capture is named splat.
|
858
|
+
It is always an array of captures, as you can use <tt>*</tt> more than once in a pattern.
|
859
|
+
</td>
|
860
|
+
</tr>
|
861
|
+
<tr>
|
862
|
+
<td><b>(</b><i>expression</i><b>)</b></td>
|
863
|
+
<td>
|
864
|
+
Enclosed <i>expression</i> is a group. Useful when combined with <tt>?</tt> to make it optional,
|
865
|
+
or to separate two elements that would otherwise be parsed as one.
|
866
|
+
</td>
|
867
|
+
</tr>
|
868
|
+
<tr>
|
869
|
+
<td><i>x</i><b>?</b></td>
|
870
|
+
<td>Makes <i>x</i> optional. For instance <tt>(foo)?</tt> matches <tt>foo</tt> or an empty string.</td>
|
871
|
+
</tr>
|
872
|
+
<tr>
|
873
|
+
<td><b>/</b></td>
|
874
|
+
<td>
|
875
|
+
Matches forward slash. Does not match URI encoded version of forward slash.
|
876
|
+
</td>
|
877
|
+
</tr>
|
878
|
+
<tr>
|
879
|
+
<td><b>\</b><i>x</i></td>
|
880
|
+
<td>Matches <i>x</i> or URI encoded version of <i>x</i>. For instance <tt>\*</tt> matches <tt>*</tt>.</td>
|
881
|
+
</tr>
|
882
|
+
<tr>
|
883
|
+
<td><i>any other character</i></td>
|
884
|
+
<td>Matches exactly that character or a URI encoded version of it.</td>
|
885
|
+
</tr>
|
886
|
+
</tbody>
|
887
|
+
</table>
|
888
|
+
|
889
|
+
### `template`
|
890
|
+
|
891
|
+
Parses fully expanded URI templates as specified by [RFC 6570](http://tools.ietf.org/html/rfc6570).
|
892
|
+
|
893
|
+
Note that it differs from URI templates in that it takes the unescaped version of special character instead of the escaped version.
|
894
|
+
|
895
|
+
``` ruby
|
896
|
+
require 'mustermann'
|
897
|
+
|
898
|
+
pattern = Mustermann.new('/{example}', type: :template)
|
899
|
+
pattern === "/foo.bar" # => true
|
900
|
+
pattern === "/foo/bar" # => false
|
901
|
+
pattern.params("/foo.bar") # => { "example" => "foo.bar" }
|
902
|
+
pattern.params("/foo/bar") # => nil
|
903
|
+
|
904
|
+
pattern = Mustermann.new("{/segments*}/{page}{.ext,cmpr:2}", type: :template)
|
905
|
+
pattern.params("/a/b/c.tar.gz") # => {"segments"=>["a","b"], "page"=>"c", "ext"=>"tar", "cmpr"=>"gz"}
|
906
|
+
```
|
907
|
+
|
908
|
+
<table>
|
909
|
+
<thead>
|
910
|
+
<tr>
|
911
|
+
<th>Syntax Element</th>
|
912
|
+
<th>Description</th>
|
913
|
+
</tr>
|
914
|
+
</thead>
|
915
|
+
<tbody>
|
916
|
+
<tr>
|
917
|
+
<td><b>{</b><i>o</i> <i>var</i> <i>m</i><b>,</b> <i>var</i> <i>m</i><b>,</b> ...<b>}</b></td>
|
918
|
+
<td>
|
919
|
+
Captures expansion.
|
920
|
+
Operator <i>o</i>: <code>+ # . / ; ? &</tt> or none.
|
921
|
+
Modifier <i>m</i>: <code>:num *</tt> or none.
|
922
|
+
</td>
|
923
|
+
</tr>
|
924
|
+
<tr>
|
925
|
+
<td><b>/</b></td>
|
926
|
+
<td>
|
927
|
+
Matches forward slash. Does not match URI encoded version of forward slash.
|
928
|
+
</td>
|
929
|
+
</tr>
|
930
|
+
<tr>
|
931
|
+
<td><i>any other character</i></td>
|
932
|
+
<td>Matches exactly that character or a URI encoded version of it.</td>
|
933
|
+
</tr>
|
934
|
+
</tbody>
|
935
|
+
</table>
|
936
|
+
|
937
|
+
The operators `+` and `#` will always match non-greedy, whereas all other operators match semi-greedy by default.
|
938
|
+
All modifiers and operators are supported. However, it does not parse lists as single values without the *explode* modifier (aka *star*).
|
939
|
+
Parametric operators (`;`, `?` and `&`) currently only match parameters in given order.
|
940
|
+
|
941
|
+
Please keep the following in mind:
|
942
|
+
|
943
|
+
> "Some URI Templates can be used in reverse for the purpose of variable matching: comparing the template to a fully formed URI in order to extract the variable parts from that URI and assign them to the named variables. Variable matching only works well if the template expressions are delimited by the beginning or end of the URI or by characters that cannot be part of the expansion, such as reserved characters surrounding a simple string expression. In general, regular expression languages are better suited for variable matching."
|
944
|
+
> — *RFC 6570, Sec 1.5: "Limitations"*
|
945
|
+
|
946
|
+
If you reuse the exact same templates and expose them via an external API meant for expansion,
|
947
|
+
you should set `uri_decode` to `false` in order to conform with the specification.
|
948
|
+
|
949
|
+
If you are looking for an alternative implementation that also supports expanding, check out [addressable](http://addressable.rubyforge.org/).
|
950
|
+
|
951
|
+
## Mapper
|
952
|
+
|
953
|
+
You can use a mapper to transform strings according to two or more mappings:
|
954
|
+
|
955
|
+
``` ruby
|
956
|
+
require 'mustermann/mapper'
|
957
|
+
|
958
|
+
mapper = Mustermann::Mapper.new("/:page(.:format)?" => ["/:page/view.:format", "/:page/view.html"])
|
959
|
+
mapper['/foo'] # => "/foo/view.html"
|
960
|
+
mapper['/foo.xml'] # => "/foo/view.xml"
|
961
|
+
mapper['/foo/bar'] # => "/foo/bar"
|
962
|
+
```
|
963
|
+
|
964
|
+
## Routers
|
965
|
+
|
966
|
+
Mustermann comes with basic router implementations that will call certain callbacks depending on the input.
|
967
|
+
|
968
|
+
### Simple Router
|
969
|
+
|
970
|
+
The simple router chooses callbacks based on an input string.
|
971
|
+
|
972
|
+
``` ruby
|
973
|
+
require 'mustermann/router/simple'
|
974
|
+
|
975
|
+
router = Mustermann::Router::Simple.new(default: 42)
|
976
|
+
router.on(':name', capture: :digit) { |string| string.to_i }
|
977
|
+
router.call("23") # => 23
|
978
|
+
router.call("example") # => 42
|
979
|
+
```
|
980
|
+
|
981
|
+
### Rack Router
|
982
|
+
|
983
|
+
This is not a full replacement for Rails, Sinatra, Cuba, etc, as it only cares about path based routing.
|
984
|
+
|
985
|
+
``` ruby
|
986
|
+
require 'mustermann/router/rack'
|
987
|
+
|
988
|
+
router = Mustermann::Router::Rack.new do
|
989
|
+
on '/' do |env|
|
990
|
+
[200, {'Content-Type' => 'text/plain'}, ['Hello World!']]
|
991
|
+
end
|
992
|
+
|
993
|
+
on '/:name' do |env|
|
994
|
+
name = env['mustermann.params']['name']
|
995
|
+
[200, {'Content-Type' => 'text/plain'}, ["Hello #{name}!"]]
|
996
|
+
end
|
997
|
+
|
998
|
+
on '/something/*', call: SomeApp
|
999
|
+
end
|
1000
|
+
|
1001
|
+
# in a config.ru
|
1002
|
+
run router
|
1003
|
+
```
|
1004
|
+
|
1005
|
+
## Requirements
|
1006
|
+
|
1007
|
+
Mustermann has no dependencies besides a Ruby 2.0 compatible Ruby implementation.
|
1008
|
+
|
1009
|
+
It is known to work on **MRI 2.0** and **MRI trunk**.
|
1010
|
+
|
1011
|
+
**JRuby** is not yet fully supported. It is possible to run large parts of Mustermann by passing in `--2.0 -X-C` starting from JRuby 1.7.4. See [issue #2](https://github.com/rkh/mustermann/issues/2) for up to date information.
|
1012
|
+
|
1013
|
+
**Rubinius** is not yet able to parse the Mustermann source code. See [issue #14](https://github.com/rkh/mustermann/issues/14) for up to date information.
|
1014
|
+
|
1015
|
+
## Release History
|
1016
|
+
|
1017
|
+
Mustermann follows [Semantic Versioning 2.0](http://semver.org/). Anything documented in the README or via YARD and not declared private is part of the public API.
|
1018
|
+
|
1019
|
+
### Stable Releases
|
1020
|
+
|
1021
|
+
There have been no stable releases yet. The code base is considered solid but I don't know of anyone using it in production yet.
|
1022
|
+
As there has been no stable release yet, the API might still change, though I consider this unlikely.
|
1023
|
+
|
1024
|
+
### Development Releases
|
1025
|
+
|
1026
|
+
* **Mustermann 0.3.1** (2014-09-12)
|
1027
|
+
* More Infos:
|
1028
|
+
[RubyGems.org](http://rubygems.org/gems/mustermann/versions/0.3.1),
|
1029
|
+
[RubyDoc.info](http://rubydoc.info/gems/mustermann/0.3.1/frames),
|
1030
|
+
[GitHub.com](https://github.com/rkh/mustermann/tree/v0.3.1)
|
1031
|
+
* Speed up pattern generation and matchin (thanks [Daniel Mendler](https://github.com/minad))
|
1032
|
+
* Small change so `Mustermann === Mustermann.new('...')` returns `true`.
|
1033
|
+
* **Mustermann 0.3.0** (2014-08-18)
|
1034
|
+
* More Infos:
|
1035
|
+
[RubyGems.org](http://rubygems.org/gems/mustermann/versions/0.3.0),
|
1036
|
+
[RubyDoc.info](http://rubydoc.info/gems/mustermann/0.3.0/frames),
|
1037
|
+
[GitHub.com](https://github.com/rkh/mustermann/tree/v0.3.0)
|
1038
|
+
* Add `regexp` pattern.
|
1039
|
+
* Add named splats to Sinatra patterns.
|
1040
|
+
* Add `Mustermann::Mapper`.
|
1041
|
+
* Improve duck typing support.
|
1042
|
+
* Improve documentation.
|
1043
|
+
* **Mustermann 0.2.0** (2013-08-24)
|
1044
|
+
* More Infos:
|
1045
|
+
[RubyGems.org](http://rubygems.org/gems/mustermann/versions/0.2.0),
|
1046
|
+
[RubyDoc.info](http://rubydoc.info/gems/mustermann/0.2.0/frames),
|
1047
|
+
[GitHub.com](https://github.com/rkh/mustermann/tree/v0.2.0)
|
1048
|
+
* Add first class expander objects.
|
1049
|
+
* Add params casting for expander.
|
1050
|
+
* Add simple router and rack router.
|
1051
|
+
* Add weak equality map to significantly improve performance.
|
1052
|
+
* Fix Ruby warnings.
|
1053
|
+
* Improve documentation.
|
1054
|
+
* Refactor pattern validation, AST transformations.
|
1055
|
+
* Increase test coverage (from 100%+ to 100%++).
|
1056
|
+
* Improve JRuby compatibility.
|
1057
|
+
* Work around bug in 2.0.0-p0.
|
1058
|
+
* **Mustermann 0.1.0** (2013-05-12)
|
1059
|
+
* More Infos:
|
1060
|
+
[RubyGems.org](http://rubygems.org/gems/mustermann/versions/0.1.0),
|
1061
|
+
[RubyDoc.info](http://rubydoc.info/gems/mustermann/0.1.0/frames),
|
1062
|
+
[GitHub.com](https://github.com/rkh/mustermann/tree/v0.1.0)
|
1063
|
+
* Add `Pattern#expand` for generating strings from patterns.
|
1064
|
+
* Add better internal API for working with the AST.
|
1065
|
+
* Improved documentation.
|
1066
|
+
* Avoids parsing the path twice when used as Sinatra extension.
|
1067
|
+
* Better exceptions for unknown pattern types.
|
1068
|
+
* Better handling of edge cases around extend.
|
1069
|
+
* More specs to ensure API stability.
|
1070
|
+
* Largely rework internals of Sinatra, Rails and Template patterns.
|
1071
|
+
* **Mustermann 0.0.1** (2013-04-27)
|
1072
|
+
* More Infos:
|
1073
|
+
[RubyGems.org](http://rubygems.org/gems/mustermann/versions/0.0.1),
|
1074
|
+
[RubyDoc.info](http://rubydoc.info/gems/mustermann/0.0.1/frames),
|
1075
|
+
[GitHub.com](https://github.com/rkh/mustermann/tree/v0.0.1)
|
1076
|
+
* Initial Release.
|
1077
|
+
|
1078
|
+
### Upcoming Releases
|
1079
|
+
|
1080
|
+
* **Mustermann 1.0.0** (before Sinatra 2.0)
|
1081
|
+
* First stable release.
|