mustermann19 0.3.1 → 0.3.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.travis.yml +4 -3
- data/README.md +680 -376
- data/lib/mustermann/ast/compiler.rb +13 -7
- data/lib/mustermann/ast/expander.rb +11 -5
- data/lib/mustermann/ast/node.rb +27 -1
- data/lib/mustermann/ast/param_scanner.rb +20 -0
- data/lib/mustermann/ast/parser.rb +131 -12
- data/lib/mustermann/ast/pattern.rb +45 -6
- data/lib/mustermann/ast/template_generator.rb +28 -0
- data/lib/mustermann/ast/validation.rb +5 -3
- data/lib/mustermann/composite.rb +103 -0
- data/lib/mustermann/expander.rb +1 -1
- data/lib/mustermann/express.rb +34 -0
- data/lib/mustermann/flask.rb +204 -0
- data/lib/mustermann/identity.rb +54 -0
- data/lib/mustermann/pattern.rb +186 -12
- data/lib/mustermann/pattern_cache.rb +49 -0
- data/lib/mustermann/pyramid.rb +25 -0
- data/lib/mustermann/regexp_based.rb +18 -1
- data/lib/mustermann/regular.rb +1 -1
- data/lib/mustermann/shell.rb +8 -0
- data/lib/mustermann/simple.rb +1 -1
- data/lib/mustermann/simple_match.rb +5 -0
- data/lib/mustermann/sinatra.rb +19 -5
- data/lib/mustermann/string_scanner.rb +314 -0
- data/lib/mustermann/template.rb +10 -0
- data/lib/mustermann/to_pattern.rb +11 -6
- data/lib/mustermann/version.rb +1 -1
- data/lib/mustermann.rb +52 -3
- data/mustermann.gemspec +1 -1
- data/spec/composite_spec.rb +147 -0
- data/spec/expander_spec.rb +15 -0
- data/spec/express_spec.rb +209 -0
- data/spec/flask_spec.rb +361 -0
- data/spec/flask_subclass_spec.rb +368 -0
- data/spec/identity_spec.rb +44 -0
- data/spec/mustermann_spec.rb +14 -0
- data/spec/pattern_spec.rb +7 -3
- data/spec/pyramid_spec.rb +101 -0
- data/spec/rails_spec.rb +76 -2
- data/spec/regular_spec.rb +25 -0
- data/spec/shell_spec.rb +33 -0
- data/spec/simple_spec.rb +25 -0
- data/spec/sinatra_spec.rb +184 -9
- data/spec/string_scanner_spec.rb +271 -0
- data/spec/support/expand_matcher.rb +7 -5
- data/spec/support/generate_template_matcher.rb +27 -0
- data/spec/support/pattern.rb +3 -0
- data/spec/support/scan_matcher.rb +63 -0
- data/spec/support.rb +2 -1
- data/spec/template_spec.rb +22 -0
- data/spec/to_pattern_spec.rb +49 -0
- metadata +47 -61
- data/internals.md +0 -64
data/README.md
CHANGED
@@ -9,7 +9,7 @@
|
|
9
9
|
|
10
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
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
|
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 keeps its runtime dependencies to a minimum and is fully covered with specs and documentation.
|
13
13
|
|
14
14
|
Given a string pattern, Mustermann will turn it into an object that behaves like a regular expression and has comparable performance characteristics.
|
15
15
|
|
@@ -23,261 +23,323 @@ when Mustermann.new('foo/*') then puts "prefixed with foo"
|
|
23
23
|
when Mustermann.new('*.pdf') then puts "it's a PDF"
|
24
24
|
when Mustermann.new('*.png') then puts "it's an image"
|
25
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
26
|
|
30
|
-
``` ruby
|
31
27
|
pattern = Mustermann.new('/:prefix/*.*')
|
32
28
|
pattern.params('/a/b.c') # => { "prefix" => "a", splat => ["b", "c"] }
|
33
29
|
```
|
34
30
|
|
35
|
-
|
31
|
+
## Overview
|
32
|
+
|
33
|
+
### Features
|
34
|
+
|
35
|
+
* **[Pattern Types](#-pattern-types):** Mustermann supports a wide variety of different pattern types, making it compatible with a large variety of existing software.
|
36
|
+
* **[Fine Grained Control](#-available-options):** You can easily adjust matching behavior and add constraints to the placeholders and capture groups.
|
37
|
+
* **[Binary Operators](#-binary-operators):** Patterns can be combined into composite patterns using binary operators.
|
38
|
+
* **[Regexp Look Alike](#-regexp-look-alike):** Mustermann patterns can be used as a replacement for regular expressions.
|
39
|
+
* **[Parameter Parsing](#-parameter-parsing):** Mustermann can parse matched parameters into a Sinatra-style "params" hash, including type casting.
|
40
|
+
* **[Peeking](#-peeking):** Lets you check if the beginning of a string matches a pattern.
|
41
|
+
* **[Expanding](#-expanding):** Besides parsing a parameters from an input string, a pattern object can also be used to generate a string from a set of parameters.
|
42
|
+
* **[Generating Templates](#-generating-templates):** This comes in handy when wanting to hand on patterns rather than fully expanded strings as part of an external API.
|
43
|
+
* **[Proc Look Alike](#-proc-look-alike):** Pass on a pattern instead of a block.
|
44
|
+
* **[Duck Typing](#-duck-typing):** You can create your own pattern-like objects by implementing `to_pattern`.
|
45
|
+
* **[Performance](#-performance):** Patterns are implemented with both performance and a low memory footprint in mind.
|
46
|
+
|
47
|
+
### Additional Tooling
|
36
48
|
|
37
|
-
|
49
|
+
These features are included in the library, but not loaded by default
|
38
50
|
|
39
|
-
|
51
|
+
* **[String Scanner](#-string-scanner):** It comes with a version of Ruby's [StringScanner](http://ruby-doc.org/stdlib-2.0/libdoc/strscan/rdoc/StringScanner.html) made for pattern objects.
|
52
|
+
* **[Mapper](#-mapper):** A simple tool for mapping one string to another based on patterns.
|
53
|
+
* **[Routers](#-routers):** Model execution flow based on pattern matching. Comes with a simple Rack router.
|
54
|
+
* **[Sinatra Integration](#-sinatra-integration):** Mustermann can be used as a [Sinatra](http://www.sinatrarb.com/) extension. Sinatra 2.0 and beyond will use Mustermann by default.
|
55
|
+
|
56
|
+
### More Infos
|
57
|
+
|
58
|
+
* **[Requirements](#-requirements):** Mustermann currently requires Ruby 2.0 or later.
|
59
|
+
* **[Release History](#-release-history):** See what's new.
|
60
|
+
|
61
|
+
<a name="-pattern-types"></a>
|
62
|
+
## Pattern Types
|
63
|
+
|
64
|
+
Mustermann support multiple pattern types. A pattern type defines the syntax, matching semantics and whether certain features, like [expanding](#-expanding) and [generating templates](#-generating-templates), are available.
|
65
|
+
|
66
|
+
You can create a pattern of a certain type by passing `type` option to `Mustermann.new`:
|
40
67
|
|
41
68
|
``` ruby
|
42
|
-
|
69
|
+
require 'mustermann'
|
70
|
+
pattern = Mustermann.new('/*/**', type: :shell)
|
43
71
|
```
|
44
72
|
|
45
|
-
|
73
|
+
Note that this will use the type as suggestion: When passing in a string argument, it will create a pattern of the given type, but it might choose a different type for other objects (a regular expression argument will always result in a [regexp](#-pattern-details-regexp) pattern, a symbol always in a [sinatra](#-pattern-details-sinatra) pattern, etc).
|
74
|
+
|
75
|
+
Alternatively, you can also load and instantiate the pattern type directly:
|
46
76
|
|
47
77
|
``` ruby
|
48
|
-
|
78
|
+
require 'mustermann/shell'
|
79
|
+
pattern = Mustermann::Shell.new('/*/**')
|
49
80
|
```
|
50
81
|
|
51
|
-
|
82
|
+
### Available Types
|
52
83
|
|
53
84
|
<table>
|
54
85
|
<thead>
|
55
86
|
<tr>
|
56
87
|
<th>Type</th>
|
57
|
-
<th>Description</th>
|
58
88
|
<th>Example</th>
|
59
|
-
<th>
|
60
|
-
<th>
|
89
|
+
<th>Compatible with</th>
|
90
|
+
<th>Notes</th>
|
61
91
|
</tr>
|
62
92
|
</thead>
|
63
93
|
<tbody>
|
94
|
+
|
64
95
|
<tr>
|
65
|
-
<th><a href="
|
66
|
-
<td
|
67
|
-
<td><
|
96
|
+
<th><a href="#-pattern-details-cake"><tt>cake</tt></a></th>
|
97
|
+
<td><tt>/:prefix/**</tt></td>
|
98
|
+
<td><a href="http://cakephp.org/">CakePHP</a></td>
|
99
|
+
<td></td>
|
100
|
+
</tr>
|
101
|
+
|
102
|
+
<tr>
|
103
|
+
<th><a href="#-pattern-details-express"><tt>express</tt></a></th>
|
104
|
+
<td><tt>/:prefix+/:id(\d+)</tt></td>
|
68
105
|
<td>
|
69
|
-
<a href="
|
70
|
-
<a href="
|
106
|
+
<a href="http://expressjs.com/">Express</a>,
|
107
|
+
<a href="https://pillarjs.github.io/">pillar.js</a>
|
71
108
|
</td>
|
72
109
|
<td></td>
|
73
110
|
</tr>
|
111
|
+
|
74
112
|
<tr>
|
75
|
-
<th><a href="
|
76
|
-
<td
|
77
|
-
<td><tt>/:slug(.:ext)</tt></td>
|
113
|
+
<th><a href="#-pattern-details-flask"><tt>flask</tt></a></th>
|
114
|
+
<td><tt>/<prefix>/<int:id></tt></td>
|
78
115
|
<td>
|
79
|
-
<a href="
|
80
|
-
<a href="
|
81
|
-
<a href="
|
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>
|
116
|
+
<a href="http://flask.pocoo.org/">Flask</a>,
|
117
|
+
<a href="http://werkzeug.pocoo.org/">Werkzeug</a>,
|
118
|
+
<a href="http://bottlepy.org/docs/dev/index.html">Bottle</a>
|
85
119
|
</td>
|
120
|
+
<td></td>
|
121
|
+
</tr>
|
122
|
+
|
123
|
+
<tr>
|
124
|
+
<th><a href="#-pattern-details-identity"><tt>identity</tt></a></th>
|
125
|
+
<td><tt>/image.png</tt></td>
|
126
|
+
<td>any software using strings</td>
|
86
127
|
<td>
|
87
|
-
|
128
|
+
Exact string matching (no parameter parsing).
|
88
129
|
</td>
|
89
130
|
</tr>
|
131
|
+
|
90
132
|
<tr>
|
91
|
-
<th><a href="
|
92
|
-
<td
|
93
|
-
<td><tt>/(?<slug>.*)</tt></td>
|
133
|
+
<th><a href="#-pattern-details-pyramid"><tt>pyramid</tt></a></th>
|
134
|
+
<td><tt>/{prefix:.*}/{id}</tt></td>
|
94
135
|
<td>
|
95
|
-
<a href="
|
96
|
-
<a href="
|
136
|
+
<a href="http://www.pylonsproject.org/projects/pyramid/about">Pyramid</a>,
|
137
|
+
<a href="http://www.pylonsproject.org/projects/pylons-framework/about">Pylons</a>
|
97
138
|
</td>
|
98
139
|
<td></td>
|
99
140
|
</tr>
|
141
|
+
|
142
|
+
<tr>
|
143
|
+
<th><a href="#-pattern-details-rails"><tt>rails</tt></a></th>
|
144
|
+
<td><tt>/:slug(.:ext)</tt></td>
|
145
|
+
<td>
|
146
|
+
<a href="http://rubyonrails.org/">Ruby on Rails</a>,
|
147
|
+
<a href="https://github.com/joshbuddy/http_router">HTTP Router</a>,
|
148
|
+
<a href="http://lotusrb.org/">Lotus</a>,
|
149
|
+
<a href="http://www.scalatra.org/">Scalatra</a> (if <a href="http://www.scalatra.org/2.3/guides/http/routes.html#toc_248">configured</a>)</td>
|
150
|
+
<td></td>
|
151
|
+
</tr>
|
152
|
+
|
100
153
|
<tr>
|
101
|
-
<th><a href="
|
102
|
-
<td
|
103
|
-
<td><tt>/*.{png,jpg}</tt></td>
|
154
|
+
<th><a href="#-pattern-details-regexp"><tt>regexp</tt></a></th>
|
155
|
+
<td><tt>/(?<slug>[^\/]+)</tt></td>
|
104
156
|
<td>
|
105
|
-
<a href="
|
106
|
-
<a href="
|
157
|
+
<a href="http://www.geocities.jp/kosako3/oniguruma/">Oniguruma</a>,
|
158
|
+
<a href="https://github.com/k-takata/Onigmo">Onigmo<a>,
|
159
|
+
regular expressions
|
107
160
|
</td>
|
108
|
-
<td
|
161
|
+
<td>
|
162
|
+
Created when you pass a regexp to <tt>Mustermann.new</tt>.<br>
|
163
|
+
Does not support expanding or generating templates.
|
164
|
+
</td>
|
165
|
+
</tr>
|
166
|
+
|
167
|
+
<tr>
|
168
|
+
<th><a href="#-pattern-details-shell"><tt>shell</tt></a></th>
|
169
|
+
<td><tt>/*.{png,jpg}</tt></td>
|
170
|
+
<td>Unix Shell (bash, zsh)</td>
|
171
|
+
<td>Does not support expanding or generating templates.</td>
|
109
172
|
</tr>
|
173
|
+
|
110
174
|
<tr>
|
111
|
-
<th><a href="
|
112
|
-
<td>Sinatra 1.3 style patterns</td>
|
175
|
+
<th><a href="#-pattern-details-simple"><tt>simple</tt></a></th>
|
113
176
|
<td><tt>/:slug.:ext</tt></td>
|
114
177
|
<td>
|
115
|
-
<a href="
|
116
|
-
<a href="
|
117
|
-
<a href="
|
118
|
-
|
178
|
+
<a href="http://www.sinatrarb.com/">Sinatra</a> (1.x),
|
179
|
+
<a href="http://www.scalatra.org/">Scalatra</a>,
|
180
|
+
<a href="http://perldancer.org/">Dancer</a>
|
181
|
+
</td>
|
182
|
+
<td>
|
183
|
+
Implementation is a direct copy from Sinatra 1.3.<br>
|
184
|
+
Does not support expanding or generating templates.
|
119
185
|
</td>
|
120
|
-
<td></td>
|
121
186
|
</tr>
|
187
|
+
|
122
188
|
<tr>
|
123
|
-
<th><a href="
|
124
|
-
<td>Sinatra 2.0 style patterns (default)</td>
|
189
|
+
<th><a href="#-pattern-details-sinatra"><tt>sinatra</tt></a></th>
|
125
190
|
<td><tt>/:slug(.:ext)?</tt></td>
|
126
191
|
<td>
|
127
|
-
<a href="
|
128
|
-
<a href="
|
129
|
-
<a href="
|
130
|
-
<a href="
|
131
|
-
<a href="#space_matches_plus"><tt>space_matches_plus</tt></a>,
|
132
|
-
<a href="#uri_decode"><tt>uri_decode</tt></a>
|
192
|
+
<a href="http://www.sinatrarb.com/">Sinatra</a> (2.x),
|
193
|
+
<a href="http://www.padrinorb.com/">Padrino</a> (>= 0.13.0),
|
194
|
+
<a href="https://github.com/namusyaka/pendragon">Pendragon</a>,
|
195
|
+
<a href="https://github.com/kenichi/angelo">Angelo</a>
|
133
196
|
</td>
|
134
197
|
<td>
|
135
|
-
<
|
198
|
+
<u>This is the default</u> and the only type "invented here".<br>
|
199
|
+
It is a superset of <tt>simple</tt> and has a common subset with
|
200
|
+
<tt>template</tt> (and others).
|
136
201
|
</td>
|
137
202
|
</tr>
|
203
|
+
|
138
204
|
<tr>
|
139
|
-
<th><a href="
|
140
|
-
<td><
|
141
|
-
<td><tt>/dictionary/{term}</tt></td>
|
205
|
+
<th><a href="#-pattern-details-sinatra"><tt>template</tt></a></th>
|
206
|
+
<td><tt>/{+pre}/{page}{?q}</tt></td>
|
142
207
|
<td>
|
143
|
-
<a href="
|
144
|
-
<a href="
|
145
|
-
<a href="
|
146
|
-
<a href="
|
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>
|
208
|
+
<a href="https://tools.ietf.org/html/rfc6570">RFC 6570</a>,
|
209
|
+
<a href="http://jsonapi.org/">JSON API</a>,
|
210
|
+
<a href="http://tools.ietf.org/html/draft-nottingham-json-home-02">JSON Home Documents</a>
|
211
|
+
and <a href="https://code.google.com/p/uri-templates/wiki/Implementations">many more</a>
|
152
212
|
</td>
|
213
|
+
<td>Standardized URI templates, can be <a href="#-generating-templates">generated</a> from most other types.</td>
|
153
214
|
</tr>
|
154
215
|
</tbody>
|
155
216
|
</table>
|
156
217
|
|
157
|
-
|
218
|
+
Any software using Mustermann is obviously compatible with at least one of the above.
|
158
219
|
|
159
|
-
|
220
|
+
### Why so many?
|
160
221
|
|
161
|
-
|
222
|
+
Short answer: **Why not?**
|
162
223
|
|
163
|
-
|
164
|
-
require 'sinatra'
|
165
|
-
require 'mustermann'
|
224
|
+
You are probably concerned about one of these issues:
|
166
225
|
|
167
|
-
|
168
|
-
|
169
|
-
end
|
170
|
-
```
|
226
|
+
* **Code base complexity:** Most of these pattern implementations are very short and simple. The `cake` definition is two lines long, the `rails` implementation four lines.
|
227
|
+
* **Memory cost:** If you don't explicitly load one of these implementations and don't create any patterns of that type, the implementation itself will not be loaded. The only memory it uses it one more entry in the list of available pattern types.
|
171
228
|
|
172
|
-
|
229
|
+
<a name="-binary-operators"></a>
|
230
|
+
## Binary Operators
|
231
|
+
|
232
|
+
Patterns can be combined via binary operators. These are:
|
233
|
+
|
234
|
+
* `|` (or): Resulting pattern matches if at least one of the input pattern matches.
|
235
|
+
* `&` (and): Resulting pattern matches if all input patterns match.
|
236
|
+
* `^` (xor): Resulting pattern matches if exactly one of the input pattern matches.
|
173
237
|
|
174
238
|
``` ruby
|
175
|
-
require 'sinatra'
|
176
239
|
require 'mustermann'
|
177
240
|
|
178
|
-
|
241
|
+
first = Mustermann.new('/foo/:input')
|
242
|
+
second = Mustermann.new('/:input/bar')
|
179
243
|
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
244
|
+
first | second === "/foo/foo" # => true
|
245
|
+
first | second === "/foo/bar" # => true
|
246
|
+
|
247
|
+
first & second === "/foo/foo" # => false
|
248
|
+
first & second === "/foo/bar" # => true
|
249
|
+
|
250
|
+
first ^ second === "/foo/foo" # => true
|
251
|
+
first ^ second === "/foo/bar" # => false
|
184
252
|
```
|
185
253
|
|
186
|
-
|
254
|
+
These resulting objects are fully functional pattern objects, allowing you to call methods like `params` or `to_proc` on them. Moreover, *or* patterns created solely from expandable patterns will also be expandable. The same logic also applies to generating templates from *or* patterns.
|
187
255
|
|
188
|
-
|
256
|
+
<a name="-regexp-look-alike"></a>
|
257
|
+
## Regexp Look Alike
|
258
|
+
|
259
|
+
Pattern objects mimic Ruby's `Regexp` class by implementing `match`, `=~`, `===`, `names` and `named_captures`.
|
189
260
|
|
190
261
|
``` ruby
|
191
|
-
require 'sinatra/base'
|
192
262
|
require 'mustermann'
|
193
263
|
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
264
|
+
pattern = Mustermann.new('/:page')
|
265
|
+
pattern.match('/') # => nil
|
266
|
+
pattern.match('/home') # => #<MatchData "/home" page:"home">
|
267
|
+
pattern =~ '/home' # => 0
|
268
|
+
pattern === '/home' # => true (this allows using it in case statements)
|
269
|
+
pattern.names # => ['page']
|
270
|
+
pattern.names # => {"page"=>[1]}
|
271
|
+
|
272
|
+
pattern = Mustermann.new('/home', type: :identity)
|
273
|
+
pattern.match('/') # => nil
|
274
|
+
pattern.match('/home') # => #<Mustermann::SimpleMatch "/home">
|
275
|
+
pattern =~ '/home' # => 0
|
276
|
+
pattern === '/home' # => true (this allows using it in case statements)
|
277
|
+
pattern.names # => []
|
278
|
+
pattern.names # => {}
|
206
279
|
```
|
207
280
|
|
208
|
-
|
281
|
+
Moreover, patterns based on regular expressions (all but `identity` and `shell`) automatically convert to regular expressions when needed:
|
209
282
|
|
210
283
|
``` ruby
|
211
|
-
require 'sinatra'
|
212
284
|
require 'mustermann'
|
213
285
|
|
214
|
-
|
215
|
-
|
286
|
+
pattern = Mustermann.new('/:page')
|
287
|
+
union = Regexp.union(pattern, /^$/)
|
216
288
|
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
params[:slug]
|
222
|
-
end
|
289
|
+
union =~ "/foo" # => 0
|
290
|
+
union =~ "" # => 0
|
291
|
+
|
292
|
+
Regexp.try_convert(pattern) # => /.../
|
223
293
|
```
|
224
294
|
|
225
|
-
|
295
|
+
This way, unless some code explicitly checks the class for a regular expression, you should be able to pass in a pattern object instead even if the code in question was not written with Mustermann in mind.
|
296
|
+
|
297
|
+
<a name="-parameter-parsing"></a>
|
298
|
+
## Parameter Parsing
|
299
|
+
|
300
|
+
Besides being a `Regexp` look-alike, Mustermann also adds a `params` method, that will give you a Sinatra-style hash:
|
226
301
|
|
227
302
|
``` ruby
|
228
|
-
require 'sinatra'
|
229
303
|
require 'mustermann'
|
230
304
|
|
231
|
-
|
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
|
305
|
+
pattern = Mustermann.new('/:prefix/*.*')
|
306
|
+
pattern.params('/a/b.c') # => { "prefix" => "a", splat => ["b", "c"] }
|
239
307
|
```
|
240
308
|
|
241
|
-
|
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.
|
309
|
+
For patterns with typed captures, it will also automatically convert them:
|
244
310
|
|
245
311
|
``` ruby
|
246
|
-
require 'sinatra/base'
|
247
|
-
require 'sinatra/respond_with'
|
248
312
|
require 'mustermann'
|
249
313
|
|
250
|
-
|
251
|
-
|
252
|
-
|
314
|
+
pattern = Mustermann.new('/<prefix>/<int:id>', type: :flask)
|
315
|
+
pattern.params('/page/10') # => { "prefix" => "page", "id" => 10 }
|
316
|
+
```
|
253
317
|
|
254
|
-
|
255
|
-
|
256
|
-
content_type params[:ext] # set Content-Type
|
257
|
-
request.path_info = params[:splat].first # drop the extension
|
258
|
-
end
|
318
|
+
<a name="-peeking"></a>
|
319
|
+
## Peeking
|
259
320
|
|
260
|
-
|
261
|
-
not_found unless page = Page.find params[:id]
|
262
|
-
respond_with(page)
|
263
|
-
end
|
264
|
-
end
|
265
|
-
```
|
321
|
+
Peeking gives the option to match a pattern against the beginning of a string rather the full string. Patterns come with four methods for peeking:
|
266
322
|
|
267
|
-
|
323
|
+
* `peek` returns the matching substring.
|
324
|
+
* `peek_size` returns the number of characters matching.
|
325
|
+
* `peek_match` will return a `MatchData` or `Mustermann::SimpleMatch` (just like `match` does for the full string)
|
326
|
+
* `peek_params` will return the `params` hash parsed from the substring and the number of characters.
|
268
327
|
|
269
|
-
|
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
|
328
|
+
All of the above will turn `nil` if there was no match.
|
274
329
|
|
275
|
-
|
330
|
+
``` ruby
|
331
|
+
require 'mustermann'
|
276
332
|
|
277
|
-
|
278
|
-
|
333
|
+
pattern = Mustermann.new('/:prefix')
|
334
|
+
pattern.peek('/foo/bar') # => '/foo'
|
335
|
+
pattern.peek_size('/foo/bar') # => 4
|
336
|
+
|
337
|
+
path_info = '/foo/bar'
|
338
|
+
params, size = patter.peek_params(path_info) # params == { "prefix" => "foo" }
|
339
|
+
rest = path_info[size..-1] # => "/bar"
|
340
|
+
```
|
279
341
|
|
280
|
-
<a name="
|
342
|
+
<a name="-expanding"></a>
|
281
343
|
## Expanding
|
282
344
|
|
283
345
|
Similarly to parsing, it is also possible to generate a string from a pattern by expanding it with a hash.
|
@@ -322,6 +384,8 @@ expander = Mustermann::Expander.new(pattern)
|
|
322
384
|
You can add patterns to an expander object via `<<`:
|
323
385
|
|
324
386
|
``` ruby
|
387
|
+
require 'mustermann'
|
388
|
+
|
325
389
|
expander = Mustermann::Expander.new
|
326
390
|
expander << "/users/:user_id"
|
327
391
|
expander << "/pages/:page_id"
|
@@ -333,6 +397,8 @@ expander.expand(page_id: 58) # => "/pages/58"
|
|
333
397
|
You can set pattern options when creating the expander:
|
334
398
|
|
335
399
|
``` ruby
|
400
|
+
require 'mustermann'
|
401
|
+
|
336
402
|
expander = Mustermann::Expander.new(type: :template)
|
337
403
|
expander << "/users/{user_id}"
|
338
404
|
expander << "/pages/{page_id}"
|
@@ -341,6 +407,8 @@ expander << "/pages/{page_id}"
|
|
341
407
|
Additionally, it is possible to combine patterns of different types:
|
342
408
|
|
343
409
|
``` ruby
|
410
|
+
require 'mustermann'
|
411
|
+
|
344
412
|
expander = Mustermann::Expander.new
|
345
413
|
expander << Mustermann.new("/users/{user_id}", type: :template)
|
346
414
|
expander << Mustermann.new("/pages/:page_id", type: :rails)
|
@@ -351,6 +419,8 @@ expander << Mustermann.new("/pages/:page_id", type: :rails)
|
|
351
419
|
The handling of additional values passed in to `expand` can be changed by setting the `additional_values` option:
|
352
420
|
|
353
421
|
``` ruby
|
422
|
+
require 'mustermann'
|
423
|
+
|
354
424
|
expander = Mustermann::Expander.new("/:slug", additional_values: :raise)
|
355
425
|
expander.expand(slug: "foo", value: "bar") # raises Mustermann::ExpandError
|
356
426
|
|
@@ -361,8 +431,233 @@ expander = Mustermann::Expander.new("/:slug", additional_values: :append)
|
|
361
431
|
expander.expand(slug: "foo", value: "bar") # => "/foo?value=bar"
|
362
432
|
```
|
363
433
|
|
434
|
+
It is also possible to pass this directly to the `expand` call:
|
435
|
+
|
436
|
+
``` ruby
|
437
|
+
require 'mustermann'
|
438
|
+
|
439
|
+
pattern = Mustermann.new('/:slug')
|
440
|
+
pattern.expan(:append, slug: "foo", value: "bar") # => "/foo?value=bar"
|
441
|
+
```
|
442
|
+
|
443
|
+
<a name="-generating-templates"></a>
|
444
|
+
## Generating Templates
|
445
|
+
|
446
|
+
... TODO ...
|
447
|
+
|
448
|
+
<a name="-proc-look-alike"></a>
|
449
|
+
## Proc Look Alike
|
450
|
+
|
451
|
+
Patterns implement `to_proc`:
|
452
|
+
|
453
|
+
``` ruby
|
454
|
+
require 'mustermann'
|
455
|
+
pattern = Mustermann.new('/foo')
|
456
|
+
callback = pattern.to_proc # => #<Proc>
|
457
|
+
|
458
|
+
callback.call('/foo') # => true
|
459
|
+
callback.call('/bar') # => false
|
460
|
+
```
|
461
|
+
|
462
|
+
They can therefore be easily passed to methods expecting a block:
|
463
|
+
|
464
|
+
``` ruby
|
465
|
+
require 'mustermann'
|
466
|
+
|
467
|
+
list = ["foo", "example@email.com", "bar"]
|
468
|
+
pattern = Mustermann.new(":name@:domain.:tld")
|
469
|
+
email = list.detect(&pattern) # => "example@email.com"
|
470
|
+
```
|
471
|
+
|
472
|
+
<a name="-string-scanner"></a>
|
473
|
+
## String Scanner
|
474
|
+
|
475
|
+
... TODO ...
|
476
|
+
|
477
|
+
<a name="-mapper"></a>
|
478
|
+
## Mapper
|
479
|
+
|
480
|
+
|
481
|
+
You can use a mapper to transform strings according to two or more mappings:
|
482
|
+
|
483
|
+
``` ruby
|
484
|
+
require 'mustermann/mapper'
|
485
|
+
|
486
|
+
mapper = Mustermann::Mapper.new("/:page(.:format)?" => ["/:page/view.:format", "/:page/view.html"])
|
487
|
+
mapper['/foo'] # => "/foo/view.html"
|
488
|
+
mapper['/foo.xml'] # => "/foo/view.xml"
|
489
|
+
mapper['/foo/bar'] # => "/foo/bar"
|
490
|
+
```
|
491
|
+
|
492
|
+
<a name="-routers"></a>
|
493
|
+
## Routers
|
494
|
+
|
495
|
+
Mustermann comes with basic router implementations that will call certain callbacks depending on the input.
|
496
|
+
|
497
|
+
### Simple Router
|
498
|
+
|
499
|
+
The simple router chooses callbacks based on an input string.
|
500
|
+
|
501
|
+
``` ruby
|
502
|
+
require 'mustermann/router/simple'
|
503
|
+
|
504
|
+
router = Mustermann::Router::Simple.new(default: 42)
|
505
|
+
router.on(':name', capture: :digit) { |string| string.to_i }
|
506
|
+
router.call("23") # => 23
|
507
|
+
router.call("example") # => 42
|
508
|
+
```
|
509
|
+
|
510
|
+
### Rack Router
|
511
|
+
|
512
|
+
This is not a full replacement for Rails, Sinatra, Cuba, etc, as it only cares about path based routing.
|
513
|
+
|
514
|
+
``` ruby
|
515
|
+
require 'mustermann/router/rack'
|
516
|
+
|
517
|
+
router = Mustermann::Router::Rack.new do
|
518
|
+
on '/' do |env|
|
519
|
+
[200, {'Content-Type' => 'text/plain'}, ['Hello World!']]
|
520
|
+
end
|
521
|
+
|
522
|
+
on '/:name' do |env|
|
523
|
+
name = env['mustermann.params']['name']
|
524
|
+
[200, {'Content-Type' => 'text/plain'}, ["Hello #{name}!"]]
|
525
|
+
end
|
526
|
+
|
527
|
+
on '/something/*', call: SomeApp
|
528
|
+
end
|
529
|
+
|
530
|
+
# in a config.ru
|
531
|
+
run router
|
532
|
+
```
|
533
|
+
<a name="-sinatra-integration"></a>
|
534
|
+
## Sinatra Integration
|
535
|
+
|
536
|
+
All patterns implement `match`, which means they can be dropped into Sinatra and other Rack routers:
|
537
|
+
|
538
|
+
``` ruby
|
539
|
+
require 'sinatra'
|
540
|
+
require 'mustermann'
|
541
|
+
|
542
|
+
get Mustermann.new('/:foo') do
|
543
|
+
params[:foo]
|
544
|
+
end
|
545
|
+
```
|
546
|
+
|
547
|
+
In fact, since using this with Sinatra is the main use case, it comes with a build-in extension for **Sinatra 1.x**.
|
548
|
+
|
549
|
+
``` ruby
|
550
|
+
require 'sinatra'
|
551
|
+
require 'mustermann'
|
552
|
+
|
553
|
+
register Mustermann
|
554
|
+
|
555
|
+
# this will use Mustermann rather than the built-in pattern matching
|
556
|
+
get '/:slug(.ext)?' do
|
557
|
+
params[:slug]
|
558
|
+
end
|
559
|
+
```
|
560
|
+
|
561
|
+
### Configuration
|
562
|
+
|
563
|
+
You can change what pattern type you want to use for your app via the `pattern` option:
|
564
|
+
|
565
|
+
``` ruby
|
566
|
+
require 'sinatra/base'
|
567
|
+
require 'mustermann'
|
568
|
+
|
569
|
+
class MyApp < Sinatra::Base
|
570
|
+
register Mustermann
|
571
|
+
set :pattern, type: :shell
|
572
|
+
|
573
|
+
get '/images/*.png' do
|
574
|
+
send_file request.path_info
|
575
|
+
end
|
576
|
+
|
577
|
+
get '/index{.htm,.html,}' do
|
578
|
+
erb :index
|
579
|
+
end
|
580
|
+
end
|
581
|
+
```
|
582
|
+
|
583
|
+
You can use the same setting for options:
|
584
|
+
|
585
|
+
``` ruby
|
586
|
+
require 'sinatra'
|
587
|
+
require 'mustermann'
|
588
|
+
|
589
|
+
register Mustermann
|
590
|
+
set :pattern, capture: { ext: %w[png jpg html txt] }
|
591
|
+
|
592
|
+
get '/:slug(.:ext)?' do
|
593
|
+
# slug will be 'foo' for '/foo.png'
|
594
|
+
# slug will be 'foo.bar' for '/foo.bar'
|
595
|
+
# slug will be 'foo.bar' for '/foo.bar.html'
|
596
|
+
params[:slug]
|
597
|
+
end
|
598
|
+
```
|
599
|
+
|
600
|
+
It is also possible to pass in options to a specific route:
|
601
|
+
|
602
|
+
``` ruby
|
603
|
+
require 'sinatra'
|
604
|
+
require 'mustermann'
|
605
|
+
|
606
|
+
register Mustermann
|
607
|
+
|
608
|
+
get '/:slug(.:ext)?', pattern: { greedy: false } do
|
609
|
+
# slug will be 'foo' for '/foo.png'
|
610
|
+
# slug will be 'foo' for '/foo.bar'
|
611
|
+
# slug will be 'foo' for '/foo.bar.html'
|
612
|
+
params[:slug]
|
613
|
+
end
|
614
|
+
```
|
615
|
+
|
616
|
+
Of course, all of the above can be combined.
|
617
|
+
Moreover, the `capture` and the `except` option can be passed to route directly.
|
618
|
+
And yes, this also works with `before` and `after` filters.
|
619
|
+
|
620
|
+
``` ruby
|
621
|
+
require 'sinatra/base'
|
622
|
+
require 'sinatra/respond_with'
|
623
|
+
require 'mustermann'
|
624
|
+
|
625
|
+
class MyApp < Sinatra::Base
|
626
|
+
register Mustermann, Sinatra::RespondWith
|
627
|
+
set :pattern, capture: { id: /\d+/ } # id will only match digits
|
628
|
+
|
629
|
+
# only capture extensions known to Rack
|
630
|
+
before '*:ext', capture: Rack::Mime::MIME_TYPES.keys do
|
631
|
+
content_type params[:ext] # set Content-Type
|
632
|
+
request.path_info = params[:splat].first # drop the extension
|
633
|
+
end
|
634
|
+
|
635
|
+
get '/:id' do
|
636
|
+
not_found unless page = Page.find params[:id]
|
637
|
+
respond_with(page)
|
638
|
+
end
|
639
|
+
end
|
640
|
+
```
|
641
|
+
|
642
|
+
### Why would I want this?
|
643
|
+
|
644
|
+
* It gives you fine grained control over the pattern matching
|
645
|
+
* Allows you to use different pattern styles in your app
|
646
|
+
* The default is more robust and powerful than the built-in patterns
|
647
|
+
* Sinatra 2.0 will use Mustermann internally
|
648
|
+
* Better exceptions for broken route syntax
|
649
|
+
|
650
|
+
### Why not include this in Sinatra 1.x?
|
651
|
+
|
652
|
+
* It would introduce breaking changes, even though these would be minor
|
653
|
+
* Like Sinatra 2.0, Mustermann requires Ruby 2.0 or newer
|
654
|
+
|
655
|
+
<a name="-duck-typing"></a>
|
364
656
|
## Duck Typing
|
365
657
|
|
658
|
+
<a name="-duck-typing-to-pattern"></a>
|
659
|
+
### `to_pattern`
|
660
|
+
|
366
661
|
All methods converting string input to pattern objects will also accept any arbitrary object that implements `to_pattern`:
|
367
662
|
|
368
663
|
``` ruby
|
@@ -405,28 +700,43 @@ end
|
|
405
700
|
MyObject.new.to_pattern # => #<Mustermann::Sinatra:"/foo">
|
406
701
|
```
|
407
702
|
|
408
|
-
|
703
|
+
<a name="-duck-typing-respond-to"></a>
|
704
|
+
### `respond_to?`
|
409
705
|
|
410
|
-
|
706
|
+
You can and should use `respond_to?` to check if a pattern supports certain features.
|
411
707
|
|
412
|
-
|
708
|
+
``` ruby
|
709
|
+
require 'mustermann'
|
710
|
+
pattern = Mustermann.new("/")
|
413
711
|
|
414
|
-
|
712
|
+
puts "supports expanding" if pattern.respond_to? :expand
|
713
|
+
puts "supports generating templates" if pattern.respond_to? :to_templates
|
714
|
+
```
|
415
715
|
|
416
|
-
|
716
|
+
Alternatively, you can handle a `NotImplementedError` raised from such a method.
|
417
717
|
|
418
718
|
``` ruby
|
419
|
-
require 'mustermann
|
420
|
-
Mustermann
|
719
|
+
require 'mustermann'
|
720
|
+
pattern = Mustermann.new("/")
|
721
|
+
|
722
|
+
begin
|
723
|
+
p pattern.to_templates
|
724
|
+
rescue NotImplementedError
|
725
|
+
puts "does not support generating templates"
|
726
|
+
end
|
421
727
|
```
|
422
728
|
|
423
|
-
|
729
|
+
This behavior corresponds to what Ruby does, for instance for [`fork`](http://ruby-doc.org/core-2.1.1/NotImplementedError.html).
|
424
730
|
|
731
|
+
<a name="-available-options"></a>
|
732
|
+
## Available Options
|
733
|
+
|
734
|
+
<a name="-available-options--capture"></a>
|
425
735
|
### `capture`
|
426
736
|
|
427
|
-
Supported by: `
|
737
|
+
Supported by: All types except `identity`, `shell` and `simple` patterns.
|
428
738
|
|
429
|
-
|
739
|
+
Most pattern types support changing the strings named captures will match via the `capture` options.
|
430
740
|
|
431
741
|
Possible values for a capture:
|
432
742
|
|
@@ -449,9 +759,10 @@ Mustermann.new('/:id.:ext', capture: { id: /\d+/, ext: ['png', 'jpg'] })
|
|
449
759
|
|
450
760
|
Available POSIX character classes are: `:alnum`, `:alpha`, `:blank`, `:cntrl`, `:digit`, `:graph`, `:lower`, `:print`, `:punct`, `:space`, `:upper`, `:xdigit`, `:word` and `:ascii`.
|
451
761
|
|
762
|
+
<a name="-available-options--except"></a>
|
452
763
|
### `except`
|
453
764
|
|
454
|
-
Supported by: `
|
765
|
+
Supported by: All types except `identity`, `shell` and `simple` patterns.
|
455
766
|
|
456
767
|
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
768
|
|
@@ -469,13 +780,15 @@ pattern === '/foo.jpg' # => true
|
|
469
780
|
pattern === '/foo.png' # => false
|
470
781
|
```
|
471
782
|
|
783
|
+
<a name="-available-options--greedy"></a>
|
472
784
|
### `greedy`
|
473
785
|
|
474
|
-
Supported by:
|
786
|
+
Supported by: All types except `identity` and `shell` patterns.
|
787
|
+
Default value: `true`
|
475
788
|
|
476
789
|
**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
790
|
|
478
|
-
**
|
791
|
+
**All other** supported 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
792
|
|
480
793
|
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
794
|
|
@@ -487,11 +800,13 @@ pattern = Mustermann.new(':a.:b', greedy: false)
|
|
487
800
|
pattern.match('a.b.c.d') # => #<MatchData a:"a" b:"b.c.d">
|
488
801
|
```
|
489
802
|
|
803
|
+
<a name="-available-options--space_matches_plus"></a>
|
490
804
|
### `space_matches_plus`
|
491
805
|
|
492
|
-
Supported by:
|
806
|
+
Supported by: All types except `identity`, `regexp` and `shell` patterns.
|
807
|
+
Default value: `true`
|
493
808
|
|
494
|
-
|
809
|
+
Most pattern types will by default also match a plus sign for a space in the pattern:
|
495
810
|
|
496
811
|
``` ruby
|
497
812
|
Mustermann.new('a b') === 'a+b' # => true
|
@@ -511,9 +826,11 @@ pattern.match('a b')[:x] # => 'a b'
|
|
511
826
|
pattern.match('a+b')[:x] # => 'a+b'
|
512
827
|
````
|
513
828
|
|
829
|
+
<a name="-available-options--uri_decode"></a>
|
514
830
|
### `uri_decode`
|
515
831
|
|
516
|
-
Supported by all
|
832
|
+
Supported by all pattern types.
|
833
|
+
Default value: `true`
|
517
834
|
|
518
835
|
Usually, characters in the pattern will also match the URI encoded version of these characters:
|
519
836
|
|
@@ -529,28 +846,134 @@ Mustermann.new('a b', uri_decode: false) === 'a b' # => true
|
|
529
846
|
Mustermann.new('a b', uri_decode: false) === 'a%20b' # => false
|
530
847
|
```
|
531
848
|
|
849
|
+
<a name="-available-options--converters"></a>
|
850
|
+
### `converters`
|
851
|
+
|
852
|
+
Supported by `flask` patterns.
|
853
|
+
Default value: `{}`
|
854
|
+
|
855
|
+
[Flask patterns](#-pattern-details-flask) support registering custom converters.
|
856
|
+
|
857
|
+
A converter object may implement any of the following methods:
|
858
|
+
|
859
|
+
* `convert`: Should return a block converting a string value to whatever value should end up in the `params` hash.
|
860
|
+
* `constraint`: Should return a regular expression limiting which input string will match the capture.
|
861
|
+
* `new`: Returns an object that may respond to `convert` and/or `constraint` as described above. Any arguments used for the converter inside the pattern will be passed to `new`.
|
862
|
+
|
863
|
+
``` ruby
|
864
|
+
require 'mustermann'
|
865
|
+
|
866
|
+
SimpleConverter = Struct.new(:constraint, :convert)
|
867
|
+
id_converter = SimpleConverter.new(/\d/, -> s { s.to_i })
|
868
|
+
|
869
|
+
class NumConverter
|
870
|
+
def initialize(base: 10)
|
871
|
+
@base = Integer(base)
|
872
|
+
end
|
873
|
+
|
874
|
+
def convert
|
875
|
+
-> s { s.to_i(@base) }
|
876
|
+
end
|
877
|
+
|
878
|
+
def constraint
|
879
|
+
@base > 10 ? /[\da-#{(@base-1).to_s(@base)}]/ : /[0-#{@base-1}]/
|
880
|
+
end
|
881
|
+
end
|
882
|
+
|
883
|
+
pattern = Mustermann.new('/<id:id>/<num(base=8):oct>/<num(base=16):hex>',
|
884
|
+
type: :flask, converters: { id: id_converter, num: NumConverter})
|
885
|
+
|
886
|
+
pattern.params('/10/12/f1') # => {"id"=>10, "oct"=>10, "hex"=>241}
|
887
|
+
```
|
888
|
+
|
889
|
+
<a name="-available-options--ignore_unknown_options"></a>
|
532
890
|
### `ignore_unknown_options`
|
533
891
|
|
534
|
-
Supported by all patterns.
|
892
|
+
Supported by all patterns.
|
893
|
+
Default value: `false`
|
535
894
|
|
536
895
|
If you pass an option in that is not supported by the specific pattern type, Mustermann will raise an `ArgumentError`.
|
537
896
|
By setting `ignore_unknown_options` to `true`, it will happily ignore the option.
|
538
897
|
|
539
|
-
|
898
|
+
<a name="-performance"></a>
|
899
|
+
## Performance
|
540
900
|
|
541
|
-
|
901
|
+
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.
|
542
902
|
|
543
|
-
|
903
|
+
Pattern objects should be treated as immutable. Their internals have been designed for both performance and low memory usage. To reduce pattern compilation, `Mustermann.new` and `Mustermann::Pattern.new` might return the same instance when given the same arguments, if that instance has not yet been garbage collected. However, this is not guaranteed, so do not rely on object identity.
|
544
904
|
|
545
|
-
|
546
|
-
require 'mustermann'
|
905
|
+
### String Matching
|
547
906
|
|
548
|
-
pattern
|
549
|
-
|
550
|
-
pattern
|
551
|
-
|
552
|
-
pattern.
|
553
|
-
|
907
|
+
When using a pattern instead of a regular expression for string matching, performance will usually be comparable.
|
908
|
+
|
909
|
+
In certain cases, Mustermann might outperform naive, equivalent regular expressions. It achieves this by using look-ahead and atomic groups in ways that work well with a backtracking, NFA-based regular expression engine (such as the Oniguruma/Onigmo engine used by Ruby). It can be difficult and error prone to construct complex regular expressions using these techniques by hand. This only applies to patterns generating an AST internally (all but [identity](#-pattern-details-identity), [shell](#-pattern-details-shell), [simple](#-pattern-details-simple) and [regexp](#-pattern-details-regexp) patterns).
|
910
|
+
|
911
|
+
When using a Mustermann pattern as a direct Regexp replacement (ie, via methods like `=~`, `match` or `===`), the overhead will be a single method dispatch, which some Ruby implementations might even eliminate with method inlining. This only applies to patterns using a regular expression internally (all but [identity](#-pattern-details-identity) and [shell](#-pattern-details-shell) patterns).
|
912
|
+
|
913
|
+
### Expanding
|
914
|
+
|
915
|
+
Pattern expansion significantly outperforms other, widely used Ruby tools for generating URLs from URL patterns in most use cases.
|
916
|
+
|
917
|
+
This comes with a few trade-offs:
|
918
|
+
|
919
|
+
* As with pattern compilation, as much computation as possible has been shifted to compiling expansion rules. This will add compilation overhead, which is why patterns only generate these rules on the first invocation to `Mustermann::Pattern#expand`. Create a `Mustermann::Expander` instance yourself to get better control over the point in time this computation should happen.
|
920
|
+
* Memory is sacrificed in favor of performance: The size of the expander object will grow linear with the number of possible combination for expansion keys ("/:foo/:bar" has one such combination, but "/(:foo/)?:bar?" has four)
|
921
|
+
* Parsing a params hash from a string generated from another params hash might not result in two identical hashes, and vice versa. Specifically, expanding ignores capture constraints, type casting and greediness.
|
922
|
+
* Partial expansion is (currently) not supported.
|
923
|
+
|
924
|
+
## Details on Pattern Types
|
925
|
+
|
926
|
+
<a name="-pattern-details-cake"></a>
|
927
|
+
### `cake`
|
928
|
+
|
929
|
+
**Supported options:**
|
930
|
+
[`capture`](#-available-options--capture),
|
931
|
+
[`except`](#-available-options--except),
|
932
|
+
[`greedy`](#-available-options--greedy),
|
933
|
+
[`space_matches_plus`](#-available-options--space_matches_plus),
|
934
|
+
[`uri_decode`](#-available-options--uri_decode),
|
935
|
+
[`ignore_unknown_options`](#-available-options--ignore_unknown_options).
|
936
|
+
|
937
|
+
**External documentation:**
|
938
|
+
[CakePHP 2.0 Routing](http://book.cakephp.org/2.0/en/development/routing.html),
|
939
|
+
[CakePHP 3.0 Routing](http://book.cakephp.org/3.0/en/development/routing.html)
|
940
|
+
|
941
|
+
<a name="-pattern-details-express"></a>
|
942
|
+
### `express`
|
943
|
+
|
944
|
+
**Supported options:**
|
945
|
+
[`capture`](#-available-options--capture),
|
946
|
+
[`except`](#-available-options--except),
|
947
|
+
[`greedy`](#-available-options--greedy),
|
948
|
+
[`space_matches_plus`](#-available-options--space_matches_plus),
|
949
|
+
[`uri_decode`](#-available-options--uri_decode),
|
950
|
+
[`ignore_unknown_options`](#-available-options--ignore_unknown_options).
|
951
|
+
|
952
|
+
**External documentation:**
|
953
|
+
[path-to-regexp](https://github.com/pillarjs/path-to-regexp#path-to-regexp),
|
954
|
+
[live demo](http://forbeslindesay.github.io/express-route-tester/)
|
955
|
+
|
956
|
+
<a name="-pattern-details-flask"></a>
|
957
|
+
### `flask`
|
958
|
+
|
959
|
+
**Supported options:**
|
960
|
+
[`capture`](#-available-options--capture),
|
961
|
+
[`except`](#-available-options--except),
|
962
|
+
[`greedy`](#-available-options--greedy),
|
963
|
+
[`space_matches_plus`](#-available-options--space_matches_plus),
|
964
|
+
[`uri_decode`](#-available-options--uri_decode),
|
965
|
+
[`converters`](#-available-options--converters),
|
966
|
+
[`ignore_unknown_options`](#-available-options--ignore_unknown_options).
|
967
|
+
|
968
|
+
**External documentation:**
|
969
|
+
[Werkzeug: URL Routing](http://werkzeug.pocoo.org/docs/0.9/routing/)
|
970
|
+
|
971
|
+
<a name="-pattern-details-identity"></a>
|
972
|
+
### `identity`
|
973
|
+
|
974
|
+
**Supported options:**
|
975
|
+
[`uri_decode`](#-available-options--uri_decode),
|
976
|
+
[`ignore_unknown_options`](#-available-options--ignore_unknown_options).
|
554
977
|
|
555
978
|
<table>
|
556
979
|
<thead>
|
@@ -567,31 +990,27 @@ pattern.params("/:example") # => {}
|
|
567
990
|
</tbody>
|
568
991
|
</table>
|
569
992
|
|
570
|
-
|
993
|
+
<a name="-pattern-details-pyramid"></a>
|
994
|
+
### `pyramid`
|
571
995
|
|
572
|
-
|
996
|
+
**Supported options:**
|
997
|
+
[`capture`](#-available-options--capture),
|
998
|
+
[`except`](#-available-options--except),
|
999
|
+
[`greedy`](#-available-options--greedy),
|
1000
|
+
[`space_matches_plus`](#-available-options--space_matches_plus),
|
1001
|
+
[`uri_decode`](#-available-options--uri_decode),
|
1002
|
+
[`ignore_unknown_options`](#-available-options--ignore_unknown_options).
|
573
1003
|
|
574
|
-
|
575
|
-
|
1004
|
+
<a name="-pattern-details-rails"></a>
|
1005
|
+
### `rails`
|
576
1006
|
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
|
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
|
-
```
|
1007
|
+
**Supported options:**
|
1008
|
+
[`capture`](#-available-options--capture),
|
1009
|
+
[`except`](#-available-options--except),
|
1010
|
+
[`greedy`](#-available-options--greedy),
|
1011
|
+
[`space_matches_plus`](#-available-options--space_matches_plus),
|
1012
|
+
[`uri_decode`](#-available-options--uri_decode),
|
1013
|
+
[`ignore_unknown_options`](#-available-options--ignore_unknown_options).
|
595
1014
|
|
596
1015
|
<table>
|
597
1016
|
<thead>
|
@@ -631,20 +1050,12 @@ pattern.params("/foo/bar") # => { "example" => "foo/bar" }
|
|
631
1050
|
</tbody>
|
632
1051
|
</table>
|
633
1052
|
|
1053
|
+
<a name="-pattern-details-regexp"></a>
|
634
1054
|
### `regexp`
|
635
1055
|
|
636
|
-
|
637
|
-
|
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
|
-
```
|
1056
|
+
**Supported options:**
|
1057
|
+
[`uri_decode`](#-available-options--uri_decode),
|
1058
|
+
[`ignore_unknown_options`](#-available-options--ignore_unknown_options).
|
648
1059
|
|
649
1060
|
<table>
|
650
1061
|
<thead>
|
@@ -661,33 +1072,12 @@ pattern.params("/foo/bar") # => { "example" => "foo/bar" }
|
|
661
1072
|
</tbody>
|
662
1073
|
</table>
|
663
1074
|
|
664
|
-
|
665
|
-
|
666
|
-
``` ruby
|
667
|
-
require 'mustermann'
|
668
|
-
Mustermann.new(/(?<example>.*)/).params("input") # => { "example" => "input" }
|
669
|
-
```
|
670
|
-
|
1075
|
+
<a name="-pattern-details-shell"></a>
|
671
1076
|
### `shell`
|
672
1077
|
|
673
|
-
|
674
|
-
|
675
|
-
|
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
|
-
```
|
1078
|
+
**Supported options:**
|
1079
|
+
[`uri_decode`](#-available-options--uri_decode),
|
1080
|
+
[`ignore_unknown_options`](#-available-options--ignore_unknown_options).
|
691
1081
|
|
692
1082
|
<table>
|
693
1083
|
<thead>
|
@@ -724,31 +1114,14 @@ pattern === "/baz" # => false
|
|
724
1114
|
</tbody>
|
725
1115
|
</table>
|
726
1116
|
|
1117
|
+
<a name="-pattern-details-simple"></a>
|
727
1118
|
### `simple`
|
728
1119
|
|
729
|
-
|
730
|
-
|
731
|
-
|
732
|
-
|
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
|
-
```
|
1120
|
+
**Supported options:**
|
1121
|
+
[`greedy`](#-available-options--greedy),
|
1122
|
+
[`space_matches_plus`](#-available-options--space_matches_plus),
|
1123
|
+
[`uri_decode`](#-available-options--uri_decode),
|
1124
|
+
[`ignore_unknown_options`](#-available-options--ignore_unknown_options).
|
752
1125
|
|
753
1126
|
<table>
|
754
1127
|
<thead>
|
@@ -792,43 +1165,16 @@ pattern.params("/foo/bar") # => { "splat" => ["foo/bar"] }
|
|
792
1165
|
</tbody>
|
793
1166
|
</table>
|
794
1167
|
|
1168
|
+
<a name="-pattern-details-sinatra"></a>
|
795
1169
|
### `sinatra`
|
796
1170
|
|
797
|
-
|
798
|
-
|
799
|
-
|
800
|
-
|
801
|
-
|
802
|
-
|
803
|
-
|
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
|
-
```
|
1171
|
+
**Supported options:**
|
1172
|
+
[`capture`](#-available-options--capture),
|
1173
|
+
[`except`](#-available-options--except),
|
1174
|
+
[`greedy`](#-available-options--greedy),
|
1175
|
+
[`space_matches_plus`](#-available-options--space_matches_plus),
|
1176
|
+
[`uri_decode`](#-available-options--uri_decode),
|
1177
|
+
[`ignore_unknown_options`](#-available-options--ignore_unknown_options).
|
832
1178
|
|
833
1179
|
<table>
|
834
1180
|
<thead>
|
@@ -839,23 +1185,23 @@ pattern.params("/foo/bar") # => { "example" => "foo/bar" }
|
|
839
1185
|
</thead>
|
840
1186
|
<tbody>
|
841
1187
|
<tr>
|
842
|
-
<td><b>:</b><i>name</i></td>
|
1188
|
+
<td><b>:</b><i>name</i> <i><b>or</b></i> <b>{</b><i>name</i><b>}</b></td>
|
843
1189
|
<td>
|
844
1190
|
Captures anything but a forward slash in a semi-greedy fashion. Capture is named <i>name</i>.
|
845
1191
|
Capture behavior can be modified with <a href="#capture"><tt>capture</tt></a> and <a href="#greedy"><tt>greedy</tt></a> option.
|
846
1192
|
</td>
|
847
1193
|
</tr>
|
848
1194
|
<tr>
|
849
|
-
<td><b>*</b><i>name</i></td>
|
1195
|
+
<td><b>*</b><i>name</i> <i><b>or</b></i> <b>{+</b><i>name</i><b>}</b></td>
|
850
1196
|
<td>
|
851
1197
|
Captures anything in a non-greedy fashion. Capture is named <i>name</i>.
|
852
1198
|
</td>
|
853
1199
|
</tr>
|
854
1200
|
<tr>
|
855
|
-
<td><b>*</b></td>
|
1201
|
+
<td><b>*</b> <i><b>or</b></i> <b>{+splat}</b></td>
|
856
1202
|
<td>
|
857
1203
|
Captures anything in a non-greedy fashion. Capture is named splat.
|
858
|
-
It is always an array of captures, as you can use
|
1204
|
+
It is always an array of captures, as you can use it more than once in a pattern.
|
859
1205
|
</td>
|
860
1206
|
</tr>
|
861
1207
|
<tr>
|
@@ -865,9 +1211,15 @@ pattern.params("/foo/bar") # => { "example" => "foo/bar" }
|
|
865
1211
|
or to separate two elements that would otherwise be parsed as one.
|
866
1212
|
</td>
|
867
1213
|
</tr>
|
1214
|
+
<tr>
|
1215
|
+
<td><b>(</b><i>expression</i><b>|</b><i>expression</i><b>|</b><i>...</i><b>)</b></td>
|
1216
|
+
<td>
|
1217
|
+
Will match anything matching the nested expressions. May contain any other syntax element, including captures.
|
1218
|
+
</td>
|
1219
|
+
</tr>
|
868
1220
|
<tr>
|
869
1221
|
<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>
|
1222
|
+
<td>Makes <i>x</i> optional. For instance, <tt>(foo)?</tt> matches <tt>foo</tt> or an empty string.</td>
|
871
1223
|
</tr>
|
872
1224
|
<tr>
|
873
1225
|
<td><b>/</b></td>
|
@@ -886,24 +1238,16 @@ pattern.params("/foo/bar") # => { "example" => "foo/bar" }
|
|
886
1238
|
</tbody>
|
887
1239
|
</table>
|
888
1240
|
|
1241
|
+
<a name="-pattern-details-template"></a>
|
889
1242
|
### `template`
|
890
1243
|
|
891
|
-
|
892
|
-
|
893
|
-
|
894
|
-
|
895
|
-
|
896
|
-
|
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
|
-
```
|
1244
|
+
**Supported options:**
|
1245
|
+
[`capture`](#-available-options--capture),
|
1246
|
+
[`except`](#-available-options--except),
|
1247
|
+
[`greedy`](#-available-options--greedy),
|
1248
|
+
[`space_matches_plus`](#-available-options--space_matches_plus),
|
1249
|
+
[`uri_decode`](#-available-options--uri_decode),
|
1250
|
+
[`ignore_unknown_options`](#-available-options--ignore_unknown_options).
|
907
1251
|
|
908
1252
|
<table>
|
909
1253
|
<thead>
|
@@ -943,82 +1287,32 @@ Please keep the following in mind:
|
|
943
1287
|
> "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
1288
|
> — *RFC 6570, Sec 1.5: "Limitations"*
|
945
1289
|
|
1290
|
+
Note that it differs from URI templates in that it takes the unescaped version of special character instead of the escaped version.
|
1291
|
+
|
946
1292
|
If you reuse the exact same templates and expose them via an external API meant for expansion,
|
947
1293
|
you should set `uri_decode` to `false` in order to conform with the specification.
|
948
1294
|
|
949
1295
|
If you are looking for an alternative implementation that also supports expanding, check out [addressable](http://addressable.rubyforge.org/).
|
950
1296
|
|
951
|
-
|
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
|
-
|
1297
|
+
<a name="-requirements"></a>
|
1005
1298
|
## Requirements
|
1006
1299
|
|
1007
|
-
Mustermann has
|
1300
|
+
Mustermann depends on [tool](https://github.com/rkh/tool) (which has been extracted from Mustermann and Sinatra 2.0), and a Ruby 2.0 compatible Ruby implementation.
|
1008
1301
|
|
1009
|
-
It is known to work on
|
1302
|
+
It is known to work on MRI 2.0 and 2.1.
|
1010
1303
|
|
1011
|
-
**JRuby** is not yet fully supported. It is possible to run
|
1304
|
+
**JRuby** is not yet fully supported. It is possible to run parts of Mustermann by passing in `--2.0 -X-C`, but as of JRuby 1.7, we would recommend waiting for proper Ruby 2.0 support to land in JRuby. The same goes for **Rubinius**.
|
1012
1305
|
|
1013
|
-
|
1306
|
+
If you need Ruby 1.9 support, you might be able to use the **unofficial** [mustermann19](http://rubygems.org/gems/mustermann19) gem based on [namusyaka's fork](https://github.com/namusyaka/mustermann).
|
1014
1307
|
|
1308
|
+
<a name="-release-history"></a>
|
1015
1309
|
## Release History
|
1016
1310
|
|
1017
1311
|
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
1312
|
|
1019
1313
|
### Stable Releases
|
1020
1314
|
|
1021
|
-
There have been no stable releases yet. The code base is considered solid but I
|
1315
|
+
There have been no stable releases yet. The code base is considered solid but I only know of a small number of actual production usage.
|
1022
1316
|
As there has been no stable release yet, the API might still change, though I consider this unlikely.
|
1023
1317
|
|
1024
1318
|
### Development Releases
|
@@ -1028,7 +1322,7 @@ As there has been no stable release yet, the API might still change, though I co
|
|
1028
1322
|
[RubyGems.org](http://rubygems.org/gems/mustermann/versions/0.3.1),
|
1029
1323
|
[RubyDoc.info](http://rubydoc.info/gems/mustermann/0.3.1/frames),
|
1030
1324
|
[GitHub.com](https://github.com/rkh/mustermann/tree/v0.3.1)
|
1031
|
-
* Speed up pattern generation and
|
1325
|
+
* Speed up pattern generation and matching (thanks [Daniel Mendler](https://github.com/minad))
|
1032
1326
|
* Small change so `Mustermann === Mustermann.new('...')` returns `true`.
|
1033
1327
|
* **Mustermann 0.3.0** (2014-08-18)
|
1034
1328
|
* More Infos:
|
@@ -1077,5 +1371,15 @@ As there has been no stable release yet, the API might still change, though I co
|
|
1077
1371
|
|
1078
1372
|
### Upcoming Releases
|
1079
1373
|
|
1374
|
+
* **Mustermann 0.4.0** (next release with new features)
|
1375
|
+
* Add `Pattern#to_proc`.
|
1376
|
+
* Add `Pattern#|`, `Pattern#&` and `Pattern#^`.
|
1377
|
+
* Add `Pattern#peek`, `Pattern#peek_size`, `Pattern#peek_match` and `Pattern#peek_params`.
|
1378
|
+
* Add `Mustermann::StringScanner`.
|
1379
|
+
* Add `Pattern#to_templates`.
|
1380
|
+
* Add `|` syntax to `sinatra` templates.
|
1381
|
+
* Add template style placeholders to `sinatra` templates.
|
1382
|
+
* Add `cake`, `express`, `flask` and `pyramid` patterns.
|
1383
|
+
* Allow passing in additional value behavior directly to `Pattern#expand`.
|
1080
1384
|
* **Mustermann 1.0.0** (before Sinatra 2.0)
|
1081
1385
|
* First stable release.
|