mustermann19 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. data/.gitignore +18 -0
  2. data/.rspec +2 -0
  3. data/.travis.yml +10 -0
  4. data/.yardopts +1 -0
  5. data/Gemfile +2 -0
  6. data/LICENSE +22 -0
  7. data/README.md +1081 -0
  8. data/Rakefile +6 -0
  9. data/bench/capturing.rb +57 -0
  10. data/bench/regexp.rb +21 -0
  11. data/bench/simple_vs_sinatra.rb +23 -0
  12. data/bench/template_vs_addressable.rb +26 -0
  13. data/internals.md +64 -0
  14. data/lib/mustermann.rb +61 -0
  15. data/lib/mustermann/ast/compiler.rb +168 -0
  16. data/lib/mustermann/ast/expander.rb +134 -0
  17. data/lib/mustermann/ast/node.rb +160 -0
  18. data/lib/mustermann/ast/parser.rb +137 -0
  19. data/lib/mustermann/ast/pattern.rb +84 -0
  20. data/lib/mustermann/ast/transformer.rb +129 -0
  21. data/lib/mustermann/ast/translator.rb +108 -0
  22. data/lib/mustermann/ast/tree_renderer.rb +29 -0
  23. data/lib/mustermann/ast/validation.rb +43 -0
  24. data/lib/mustermann/caster.rb +117 -0
  25. data/lib/mustermann/equality_map.rb +48 -0
  26. data/lib/mustermann/error.rb +6 -0
  27. data/lib/mustermann/expander.rb +206 -0
  28. data/lib/mustermann/extension.rb +52 -0
  29. data/lib/mustermann/identity.rb +19 -0
  30. data/lib/mustermann/mapper.rb +98 -0
  31. data/lib/mustermann/pattern.rb +182 -0
  32. data/lib/mustermann/rails.rb +17 -0
  33. data/lib/mustermann/regexp_based.rb +30 -0
  34. data/lib/mustermann/regular.rb +26 -0
  35. data/lib/mustermann/router.rb +9 -0
  36. data/lib/mustermann/router/rack.rb +50 -0
  37. data/lib/mustermann/router/simple.rb +144 -0
  38. data/lib/mustermann/shell.rb +29 -0
  39. data/lib/mustermann/simple.rb +38 -0
  40. data/lib/mustermann/simple_match.rb +30 -0
  41. data/lib/mustermann/sinatra.rb +22 -0
  42. data/lib/mustermann/template.rb +48 -0
  43. data/lib/mustermann/to_pattern.rb +45 -0
  44. data/lib/mustermann/version.rb +3 -0
  45. data/mustermann.gemspec +31 -0
  46. data/spec/expander_spec.rb +105 -0
  47. data/spec/extension_spec.rb +296 -0
  48. data/spec/identity_spec.rb +83 -0
  49. data/spec/mapper_spec.rb +83 -0
  50. data/spec/mustermann_spec.rb +65 -0
  51. data/spec/pattern_spec.rb +49 -0
  52. data/spec/rails_spec.rb +522 -0
  53. data/spec/regexp_based_spec.rb +8 -0
  54. data/spec/regular_spec.rb +36 -0
  55. data/spec/router/rack_spec.rb +39 -0
  56. data/spec/router/simple_spec.rb +32 -0
  57. data/spec/shell_spec.rb +109 -0
  58. data/spec/simple_match_spec.rb +10 -0
  59. data/spec/simple_spec.rb +237 -0
  60. data/spec/sinatra_spec.rb +574 -0
  61. data/spec/support.rb +5 -0
  62. data/spec/support/coverage.rb +16 -0
  63. data/spec/support/env.rb +15 -0
  64. data/spec/support/expand_matcher.rb +27 -0
  65. data/spec/support/match_matcher.rb +39 -0
  66. data/spec/support/pattern.rb +39 -0
  67. data/spec/template_spec.rb +815 -0
  68. data/spec/to_pattern_spec.rb +20 -0
  69. metadata +301 -0
@@ -0,0 +1,5 @@
1
+ require 'support/env'
2
+ require 'support/coverage'
3
+ require 'support/expand_matcher'
4
+ require 'support/match_matcher'
5
+ require 'support/pattern'
@@ -0,0 +1,16 @@
1
+ require 'simplecov'
2
+ require 'coveralls'
3
+
4
+ SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
5
+ SimpleCov::Formatter::HTMLFormatter,
6
+ Coveralls::SimpleCov::Formatter
7
+ ]
8
+
9
+ SimpleCov.start do
10
+ project_name 'mustermann'
11
+ minimum_coverage 100
12
+ coverage_dir '.coverage'
13
+
14
+ add_filter "/spec/"
15
+ add_group 'Library', 'lib'
16
+ end
@@ -0,0 +1,15 @@
1
+ #if RUBY_VERSION < '2.0.0'
2
+ # $stderr.puts "needs Ruby 2.0.0, you're running #{RUBY_VERSION}"
3
+ # exit 1
4
+ #end
5
+ require 'rspec'
6
+ require 'rspec/its'
7
+
8
+ ENV['RACK_ENV'] = 'test'
9
+
10
+ RSpec.configure do |config|
11
+ config.filter_run_excluding :skip => true
12
+ config.expect_with :rspec do |c|
13
+ c.syntax = [:should, :expect]
14
+ end
15
+ end
@@ -0,0 +1,27 @@
1
+ RSpec::Matchers.define :expand do |values = {}|
2
+ match do |pattern|
3
+ @string ||= nil
4
+ begin
5
+ expanded = pattern.expand(values)
6
+ rescue Exception
7
+ false
8
+ else
9
+ @string ? @string == expanded : !!expanded
10
+ end
11
+ end
12
+
13
+ chain :to do |string|
14
+ @string = string
15
+ end
16
+
17
+ failure_message do |pattern|
18
+ message = "expected %p to be expandable with %p" % [pattern, values]
19
+ expanded = pattern.expand(values)
20
+ message << " and result in %p, but got %p" % [@string, expanded] if @string
21
+ message
22
+ end
23
+
24
+ failure_message_when_negated do |pattern|
25
+ "expected %p not to be expandable with %p" % [pattern, values]
26
+ end
27
+ end
@@ -0,0 +1,39 @@
1
+ RSpec::Matchers.define :match do |expected|
2
+ match do |actual|
3
+ @captures ||= false
4
+ match = actual.match(expected)
5
+ match &&= @captures.all? { |k, v| match[k] == v } if @captures
6
+ match
7
+ end
8
+
9
+ chain :capturing do |captures|
10
+ @captures = captures
11
+ end
12
+
13
+ failure_message do |actual|
14
+ require 'pp'
15
+ match = actual.match(expected)
16
+ if match
17
+ key, value = @captures.detect { |k, v| match[k] != v }
18
+ message = "expected %p to capture %p as %p when matching %p, but got %p\n\nRegular Expression:\n%p" % [
19
+ actual.to_s, key, value, expected, match[key], actual.regexp
20
+ ]
21
+
22
+ if ast = actual.instance_variable_get(:@ast)
23
+ require 'mustermann/ast/tree_renderer'
24
+ tree = Mustermann::AST::TreeRenderer.render(ast)
25
+ message << "\n\nAST:\n#{tree}"
26
+ end
27
+
28
+ message
29
+ else
30
+ "expected %p to match %p" % [ actual, expected ]
31
+ end
32
+ end
33
+
34
+ failure_message_when_negated do |actual|
35
+ "expected %p not to match %p" % [
36
+ actual.to_s, expected
37
+ ]
38
+ end
39
+ end
@@ -0,0 +1,39 @@
1
+ require 'timeout'
2
+
3
+ module Support
4
+ module Pattern
5
+ def pattern(pattern, options = nil, &block)
6
+ description = "pattern %p" % pattern
7
+
8
+ if options
9
+ description << " with options %p" % [options]
10
+ instance = subject_for(pattern, options)
11
+ else
12
+ instance = subject_for(pattern)
13
+ end
14
+
15
+ context description do
16
+ subject(:pattern, &instance)
17
+ its(:to_s) { should be == pattern }
18
+ its(:inspect) { should be == "#<#{described_class}:#{pattern.inspect}>" }
19
+ its(:names) { should be_an(Array) }
20
+
21
+ example 'string should be immune to external change' do
22
+ subject.to_s.replace "NOT THE PATTERN"
23
+ subject.to_s.should be == pattern
24
+ end
25
+
26
+ instance_eval(&block)
27
+ end
28
+ end
29
+
30
+ def subject_for(pattern, *args)
31
+ instance = Timeout.timeout(1) { described_class.new(pattern, *args) }
32
+ proc { instance }
33
+ rescue Timeout::Error => error
34
+ proc { raise Timeout::Error, "could not compile #{pattern.inspect} in time", error.backtrace }
35
+ rescue Exception => error
36
+ proc { raise error }
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,815 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require 'support'
3
+ require 'mustermann/template'
4
+
5
+ describe Mustermann::Template do
6
+ extend Support::Pattern
7
+
8
+ pattern '' do
9
+ it { should match('') }
10
+ it { should_not match('/') }
11
+ end
12
+
13
+ pattern '/' do
14
+ it { should match('/') }
15
+ it { should_not match('/foo') }
16
+ end
17
+
18
+ pattern '/foo' do
19
+ it { should match('/foo') }
20
+ it { should_not match('/bar') }
21
+ it { should_not match('/foo.bar') }
22
+ end
23
+
24
+ pattern '/foo/bar' do
25
+ it { should match('/foo/bar') }
26
+ it { should_not match('/foo%2Fbar') }
27
+ it { should_not match('/foo%2fbar') }
28
+ end
29
+
30
+ pattern '/:foo' do
31
+ it { should match('/:foo') }
32
+ it { should match('/%3Afoo') }
33
+ it { should_not match('/foo') }
34
+ it { should_not match('/foo?') }
35
+ it { should_not match('/foo/bar') }
36
+ it { should_not match('/') }
37
+ it { should_not match('/foo/') }
38
+ end
39
+
40
+ pattern '/föö' do
41
+ it { should match("/f%C3%B6%C3%B6") }
42
+ end
43
+
44
+ pattern '/test$/' do
45
+ it { should match('/test$/') }
46
+ end
47
+
48
+ pattern '/te+st/' do
49
+ it { should match('/te+st/') }
50
+ it { should_not match('/test/') }
51
+ it { should_not match('/teest/') }
52
+ end
53
+
54
+ pattern "/path with spaces" do
55
+ it { should match('/path%20with%20spaces') }
56
+ it { should match('/path%2Bwith%2Bspaces') }
57
+ it { should match('/path+with+spaces') }
58
+ end
59
+
60
+ pattern '/foo&bar' do
61
+ it { should match('/foo&bar') }
62
+ end
63
+
64
+ pattern '/test.bar' do
65
+ it { should match('/test.bar') }
66
+ it { should_not match('/test0bar') }
67
+ end
68
+
69
+ pattern "/path with spaces", space_matches_plus: false do
70
+ it { should match('/path%20with%20spaces') }
71
+ it { should_not match('/path%2Bwith%2Bspaces') }
72
+ it { should_not match('/path+with+spaces') }
73
+ end
74
+
75
+ pattern "/path with spaces", uri_decode: false do
76
+ it { should_not match('/path%20with%20spaces') }
77
+ it { should_not match('/path%2Bwith%2Bspaces') }
78
+ it { should_not match('/path+with+spaces') }
79
+ end
80
+
81
+ context 'level 1' do
82
+ context 'without operator' do
83
+ pattern '/hello/{person}' do
84
+ it { should match('/hello/Frank').capturing person: 'Frank' }
85
+ it { should match('/hello/a_b~c').capturing person: 'a_b~c' }
86
+ it { should match('/hello/a.%20').capturing person: 'a.%20' }
87
+
88
+ it { should_not match('/hello/:') }
89
+ it { should_not match('/hello//') }
90
+ it { should_not match('/hello/?') }
91
+ it { should_not match('/hello/#') }
92
+ it { should_not match('/hello/[') }
93
+ it { should_not match('/hello/]') }
94
+ it { should_not match('/hello/@') }
95
+ it { should_not match('/hello/!') }
96
+ it { should_not match('/hello/*') }
97
+ it { should_not match('/hello/+') }
98
+ it { should_not match('/hello/,') }
99
+ it { should_not match('/hello/;') }
100
+ it { should_not match('/hello/=') }
101
+
102
+ example { pattern.params('/hello/Frank').should be == {'person' => 'Frank'} }
103
+ end
104
+
105
+ pattern "/{foo}/{bar}" do
106
+ it { should match('/foo/bar') .capturing foo: 'foo', bar: 'bar' }
107
+ it { should match('/foo.bar/bar.foo') .capturing foo: 'foo.bar', bar: 'bar.foo' }
108
+ it { should match('/10.1/te.st') .capturing foo: '10.1', bar: 'te.st' }
109
+ it { should match('/10.1.2/te.st') .capturing foo: '10.1.2', bar: 'te.st' }
110
+
111
+ it { should_not match('/foo%2Fbar') }
112
+ it { should_not match('/foo%2fbar') }
113
+ end
114
+ end
115
+ end
116
+
117
+ context 'level 2' do
118
+ context 'operator +' do
119
+ pattern '/hello/{+person}' do
120
+ it { should match('/hello/Frank') .capturing person: 'Frank' }
121
+ it { should match('/hello/a_b~c') .capturing person: 'a_b~c' }
122
+ it { should match('/hello/a.%20') .capturing person: 'a.%20' }
123
+ it { should match('/hello/a/%20') .capturing person: 'a/%20' }
124
+ it { should match('/hello/:') .capturing person: ?: }
125
+ it { should match('/hello//') .capturing person: ?/ }
126
+ it { should match('/hello/?') .capturing person: ?? }
127
+ it { should match('/hello/#') .capturing person: ?# }
128
+ it { should match('/hello/[') .capturing person: ?[ }
129
+ it { should match('/hello/]') .capturing person: ?] }
130
+ it { should match('/hello/@') .capturing person: ?@ }
131
+ it { should match('/hello/!') .capturing person: ?! }
132
+ it { should match('/hello/*') .capturing person: ?* }
133
+ it { should match('/hello/+') .capturing person: ?+ }
134
+ it { should match('/hello/,') .capturing person: ?, }
135
+ it { should match('/hello/;') .capturing person: ?; }
136
+ it { should match('/hello/=') .capturing person: ?= }
137
+ end
138
+
139
+ pattern "/{+foo}/{bar}" do
140
+ it { should match('/foo/bar') .capturing foo: 'foo', bar: 'bar' }
141
+ it { should match('/foo.bar/bar.foo') .capturing foo: 'foo.bar', bar: 'bar.foo' }
142
+ it { should match('/foo/bar/bar.foo') .capturing foo: 'foo/bar', bar: 'bar.foo' }
143
+ it { should match('/10.1/te.st') .capturing foo: '10.1', bar: 'te.st' }
144
+ it { should match('/10.1.2/te.st') .capturing foo: '10.1.2', bar: 'te.st' }
145
+
146
+ it { should_not match('/foo%2Fbar') }
147
+ it { should_not match('/foo%2fbar') }
148
+ end
149
+ end
150
+
151
+ context 'operator #' do
152
+ pattern '/hello/{#person}' do
153
+ it { should match('/hello/#Frank') .capturing person: 'Frank' }
154
+ it { should match('/hello/#a_b~c') .capturing person: 'a_b~c' }
155
+ it { should match('/hello/#a.%20') .capturing person: 'a.%20' }
156
+ it { should match('/hello/#a/%20') .capturing person: 'a/%20' }
157
+ it { should match('/hello/#:') .capturing person: ?: }
158
+ it { should match('/hello/#/') .capturing person: ?/ }
159
+ it { should match('/hello/#?') .capturing person: ?? }
160
+ it { should match('/hello/##') .capturing person: ?# }
161
+ it { should match('/hello/#[') .capturing person: ?[ }
162
+ it { should match('/hello/#]') .capturing person: ?] }
163
+ it { should match('/hello/#@') .capturing person: ?@ }
164
+ it { should match('/hello/#!') .capturing person: ?! }
165
+ it { should match('/hello/#*') .capturing person: ?* }
166
+ it { should match('/hello/#+') .capturing person: ?+ }
167
+ it { should match('/hello/#,') .capturing person: ?, }
168
+ it { should match('/hello/#;') .capturing person: ?; }
169
+ it { should match('/hello/#=') .capturing person: ?= }
170
+
171
+
172
+ it { should_not match('/hello/Frank') }
173
+ it { should_not match('/hello/a_b~c') }
174
+ it { should_not match('/hello/a.%20') }
175
+
176
+ it { should_not match('/hello/:') }
177
+ it { should_not match('/hello//') }
178
+ it { should_not match('/hello/?') }
179
+ it { should_not match('/hello/#') }
180
+ it { should_not match('/hello/[') }
181
+ it { should_not match('/hello/]') }
182
+ it { should_not match('/hello/@') }
183
+ it { should_not match('/hello/!') }
184
+ it { should_not match('/hello/*') }
185
+ it { should_not match('/hello/+') }
186
+ it { should_not match('/hello/,') }
187
+ it { should_not match('/hello/;') }
188
+ it { should_not match('/hello/=') }
189
+
190
+
191
+ example { pattern.params('/hello/#Frank').should be == {'person' => 'Frank'} }
192
+ end
193
+
194
+ pattern "/{+foo}/{#bar}" do
195
+ it { should match('/foo/#bar') .capturing foo: 'foo', bar: 'bar' }
196
+ it { should match('/foo.bar/#bar.foo') .capturing foo: 'foo.bar', bar: 'bar.foo' }
197
+ it { should match('/foo/bar/#bar.foo') .capturing foo: 'foo/bar', bar: 'bar.foo' }
198
+ it { should match('/10.1/#te.st') .capturing foo: '10.1', bar: 'te.st' }
199
+ it { should match('/10.1.2/#te.st') .capturing foo: '10.1.2', bar: 'te.st' }
200
+
201
+ it { should_not match('/foo%2F#bar') }
202
+ it { should_not match('/foo%2f#bar') }
203
+
204
+ example { pattern.params('/hello/#Frank').should be == {'foo' => 'hello', 'bar' => 'Frank'} }
205
+ end
206
+ end
207
+ end
208
+
209
+ context 'level 3' do
210
+ context 'without operator' do
211
+ pattern "{a,b,c}" do
212
+ it { should match("~x,42,_").capturing a: '~x', b: '42', c: '_' }
213
+ it { should_not match("~x,42") }
214
+ it { should_not match("~x/42") }
215
+ it { should_not match("~x#42") }
216
+ it { should_not match("~x,42,_#42") }
217
+
218
+ example { pattern.params('d,f,g').should be == {'a' => 'd', 'b' => 'f', 'c' => 'g'} }
219
+ end
220
+ end
221
+
222
+ context 'operator +' do
223
+ pattern "{+a,b,c}" do
224
+ it { should match("~x,42,_") .capturing a: '~x', b: '42', c: '_' }
225
+ it { should match("~x,42,_#42") .capturing a: '~x', b: '42', c: '_#42' }
226
+ it { should match("~/x,42,_/42") .capturing a: '~/x', b: '42', c: '_/42' }
227
+
228
+ it { should_not match("~x,42") }
229
+ it { should_not match("~x/42") }
230
+ it { should_not match("~x#42") }
231
+ end
232
+ end
233
+
234
+ context 'operator #' do
235
+ pattern "{#a,b,c}" do
236
+ it { should match("#~x,42,_") .capturing a: '~x', b: '42', c: '_' }
237
+ it { should match("#~x,42,_#42") .capturing a: '~x', b: '42', c: '_#42' }
238
+ it { should match("#~/x,42,_#42") .capturing a: '~/x', b: '42', c: '_#42' }
239
+
240
+ it { should_not match("~x,42,_") }
241
+ it { should_not match("~x,42,_#42") }
242
+ it { should_not match("~/x,42,_#42") }
243
+
244
+ it { should_not match("~x,42") }
245
+ it { should_not match("~x/42") }
246
+ it { should_not match("~x#42") }
247
+ end
248
+ end
249
+
250
+ context 'operator .' do
251
+ pattern '/hello/{.person}' do
252
+ it { should match('/hello/.Frank') .capturing person: 'Frank' }
253
+ it { should match('/hello/.a_b~c') .capturing person: 'a_b~c' }
254
+
255
+ it { should_not match('/hello/.:') }
256
+ it { should_not match('/hello/./') }
257
+ it { should_not match('/hello/.?') }
258
+ it { should_not match('/hello/.#') }
259
+ it { should_not match('/hello/.[') }
260
+ it { should_not match('/hello/.]') }
261
+ it { should_not match('/hello/.@') }
262
+ it { should_not match('/hello/.!') }
263
+ it { should_not match('/hello/.*') }
264
+ it { should_not match('/hello/.+') }
265
+ it { should_not match('/hello/.,') }
266
+ it { should_not match('/hello/.;') }
267
+ it { should_not match('/hello/.=') }
268
+
269
+ it { should_not match('/hello/Frank') }
270
+ it { should_not match('/hello/a_b~c') }
271
+ it { should_not match('/hello/a.%20') }
272
+
273
+ it { should_not match('/hello/:') }
274
+ it { should_not match('/hello//') }
275
+ it { should_not match('/hello/?') }
276
+ it { should_not match('/hello/#') }
277
+ it { should_not match('/hello/[') }
278
+ it { should_not match('/hello/]') }
279
+ it { should_not match('/hello/@') }
280
+ it { should_not match('/hello/!') }
281
+ it { should_not match('/hello/*') }
282
+ it { should_not match('/hello/+') }
283
+ it { should_not match('/hello/,') }
284
+ it { should_not match('/hello/;') }
285
+ it { should_not match('/hello/=') }
286
+ end
287
+
288
+ pattern "{.a,b,c}" do
289
+ it { should match(".~x.42._").capturing a: '~x', b: '42', c: '_' }
290
+ it { should_not match(".~x,42") }
291
+ it { should_not match(".~x/42") }
292
+ it { should_not match(".~x#42") }
293
+ it { should_not match(".~x,42,_") }
294
+ it { should_not match("~x.42._") }
295
+ end
296
+ end
297
+
298
+ context 'operator /' do
299
+ pattern '/hello{/person}' do
300
+ it { should match('/hello/Frank') .capturing person: 'Frank' }
301
+ it { should match('/hello/a_b~c') .capturing person: 'a_b~c' }
302
+
303
+ it { should_not match('/hello//:') }
304
+ it { should_not match('/hello///') }
305
+ it { should_not match('/hello//?') }
306
+ it { should_not match('/hello//#') }
307
+ it { should_not match('/hello//[') }
308
+ it { should_not match('/hello//]') }
309
+ it { should_not match('/hello//@') }
310
+ it { should_not match('/hello//!') }
311
+ it { should_not match('/hello//*') }
312
+ it { should_not match('/hello//+') }
313
+ it { should_not match('/hello//,') }
314
+ it { should_not match('/hello//;') }
315
+ it { should_not match('/hello//=') }
316
+
317
+ it { should_not match('/hello/:') }
318
+ it { should_not match('/hello//') }
319
+ it { should_not match('/hello/?') }
320
+ it { should_not match('/hello/#') }
321
+ it { should_not match('/hello/[') }
322
+ it { should_not match('/hello/]') }
323
+ it { should_not match('/hello/@') }
324
+ it { should_not match('/hello/!') }
325
+ it { should_not match('/hello/*') }
326
+ it { should_not match('/hello/+') }
327
+ it { should_not match('/hello/,') }
328
+ it { should_not match('/hello/;') }
329
+ it { should_not match('/hello/=') }
330
+ end
331
+
332
+ pattern "{/a,b,c}" do
333
+ it { should match("/~x/42/_").capturing a: '~x', b: '42', c: '_' }
334
+ it { should_not match("/~x,42") }
335
+ it { should_not match("/~x.42") }
336
+ it { should_not match("/~x#42") }
337
+ it { should_not match("/~x,42,_") }
338
+ it { should_not match("~x/42/_") }
339
+ end
340
+ end
341
+
342
+ context 'operator ;' do
343
+ pattern '/hello/{;person}' do
344
+ it { should match('/hello/;person=Frank') .capturing person: 'Frank' }
345
+ it { should match('/hello/;person=a_b~c') .capturing person: 'a_b~c' }
346
+ it { should match('/hello/;person') .capturing person: nil }
347
+
348
+ it { should_not match('/hello/;persona=Frank') }
349
+ it { should_not match('/hello/;persona=a_b~c') }
350
+
351
+ it { should_not match('/hello/;person=:') }
352
+ it { should_not match('/hello/;person=/') }
353
+ it { should_not match('/hello/;person=?') }
354
+ it { should_not match('/hello/;person=#') }
355
+ it { should_not match('/hello/;person=[') }
356
+ it { should_not match('/hello/;person=]') }
357
+ it { should_not match('/hello/;person=@') }
358
+ it { should_not match('/hello/;person=!') }
359
+ it { should_not match('/hello/;person=*') }
360
+ it { should_not match('/hello/;person=+') }
361
+ it { should_not match('/hello/;person=,') }
362
+ it { should_not match('/hello/;person=;') }
363
+ it { should_not match('/hello/;person==') }
364
+
365
+ it { should_not match('/hello/;Frank') }
366
+ it { should_not match('/hello/;a_b~c') }
367
+ it { should_not match('/hello/;a.%20') }
368
+
369
+ it { should_not match('/hello/:') }
370
+ it { should_not match('/hello//') }
371
+ it { should_not match('/hello/?') }
372
+ it { should_not match('/hello/#') }
373
+ it { should_not match('/hello/[') }
374
+ it { should_not match('/hello/]') }
375
+ it { should_not match('/hello/@') }
376
+ it { should_not match('/hello/!') }
377
+ it { should_not match('/hello/*') }
378
+ it { should_not match('/hello/+') }
379
+ it { should_not match('/hello/,') }
380
+ it { should_not match('/hello/;') }
381
+ it { should_not match('/hello/=') }
382
+ end
383
+
384
+ pattern "{;a,b,c}" do
385
+ it { should match(";a=~x;b=42;c=_") .capturing a: '~x', b: '42', c: '_' }
386
+ it { should match(";a=~x;b;c=_") .capturing a: '~x', b: nil, c: '_' }
387
+
388
+ it { should_not match(";a=~x;c=_;b=42").capturing a: '~x', b: '42', c: '_' }
389
+
390
+ it { should_not match(";a=~x;b=42") }
391
+ it { should_not match("a=~x;b=42") }
392
+ it { should_not match(";a=~x;b=#42;c") }
393
+ it { should_not match(";a=~x,b=42,c=_") }
394
+ it { should_not match("~x;b=42;c=_") }
395
+ end
396
+ end
397
+
398
+ context 'operator ?' do
399
+ pattern '/hello/{?person}' do
400
+ it { should match('/hello/?person=Frank') .capturing person: 'Frank' }
401
+ it { should match('/hello/?person=a_b~c') .capturing person: 'a_b~c' }
402
+ it { should match('/hello/?person') .capturing person: nil }
403
+
404
+ it { should_not match('/hello/?persona=Frank') }
405
+ it { should_not match('/hello/?persona=a_b~c') }
406
+
407
+ it { should_not match('/hello/?person=:') }
408
+ it { should_not match('/hello/?person=/') }
409
+ it { should_not match('/hello/?person=?') }
410
+ it { should_not match('/hello/?person=#') }
411
+ it { should_not match('/hello/?person=[') }
412
+ it { should_not match('/hello/?person=]') }
413
+ it { should_not match('/hello/?person=@') }
414
+ it { should_not match('/hello/?person=!') }
415
+ it { should_not match('/hello/?person=*') }
416
+ it { should_not match('/hello/?person=+') }
417
+ it { should_not match('/hello/?person=,') }
418
+ it { should_not match('/hello/?person=;') }
419
+ it { should_not match('/hello/?person==') }
420
+
421
+ it { should_not match('/hello/?Frank') }
422
+ it { should_not match('/hello/?a_b~c') }
423
+ it { should_not match('/hello/?a.%20') }
424
+
425
+ it { should_not match('/hello/:') }
426
+ it { should_not match('/hello//') }
427
+ it { should_not match('/hello/?') }
428
+ it { should_not match('/hello/#') }
429
+ it { should_not match('/hello/[') }
430
+ it { should_not match('/hello/]') }
431
+ it { should_not match('/hello/@') }
432
+ it { should_not match('/hello/!') }
433
+ it { should_not match('/hello/*') }
434
+ it { should_not match('/hello/+') }
435
+ it { should_not match('/hello/,') }
436
+ it { should_not match('/hello/;') }
437
+ it { should_not match('/hello/=') }
438
+ end
439
+
440
+ pattern "{?a,b,c}" do
441
+ it { should match("?a=~x&b=42&c=_") .capturing a: '~x', b: '42', c: '_' }
442
+ it { should match("?a=~x&b&c=_") .capturing a: '~x', b: nil, c: '_' }
443
+
444
+ it { should_not match("?a=~x&c=_&b=42").capturing a: '~x', b: '42', c: '_' }
445
+
446
+ it { should_not match("?a=~x&b=42") }
447
+ it { should_not match("a=~x&b=42") }
448
+ it { should_not match("?a=~x&b=#42&c") }
449
+ it { should_not match("?a=~x,b=42,c=_") }
450
+ it { should_not match("~x&b=42&c=_") }
451
+ end
452
+ end
453
+
454
+ context 'operator &' do
455
+ pattern '/hello/{&person}' do
456
+ it { should match('/hello/&person=Frank') .capturing person: 'Frank' }
457
+ it { should match('/hello/&person=a_b~c') .capturing person: 'a_b~c' }
458
+ it { should match('/hello/&person') .capturing person: nil }
459
+
460
+ it { should_not match('/hello/&persona=Frank') }
461
+ it { should_not match('/hello/&persona=a_b~c') }
462
+
463
+ it { should_not match('/hello/&person=:') }
464
+ it { should_not match('/hello/&person=/') }
465
+ it { should_not match('/hello/&person=?') }
466
+ it { should_not match('/hello/&person=#') }
467
+ it { should_not match('/hello/&person=[') }
468
+ it { should_not match('/hello/&person=]') }
469
+ it { should_not match('/hello/&person=@') }
470
+ it { should_not match('/hello/&person=!') }
471
+ it { should_not match('/hello/&person=*') }
472
+ it { should_not match('/hello/&person=+') }
473
+ it { should_not match('/hello/&person=,') }
474
+ it { should_not match('/hello/&person=;') }
475
+ it { should_not match('/hello/&person==') }
476
+
477
+ it { should_not match('/hello/&Frank') }
478
+ it { should_not match('/hello/&a_b~c') }
479
+ it { should_not match('/hello/&a.%20') }
480
+
481
+ it { should_not match('/hello/:') }
482
+ it { should_not match('/hello//') }
483
+ it { should_not match('/hello/?') }
484
+ it { should_not match('/hello/#') }
485
+ it { should_not match('/hello/[') }
486
+ it { should_not match('/hello/]') }
487
+ it { should_not match('/hello/@') }
488
+ it { should_not match('/hello/!') }
489
+ it { should_not match('/hello/*') }
490
+ it { should_not match('/hello/+') }
491
+ it { should_not match('/hello/,') }
492
+ it { should_not match('/hello/;') }
493
+ it { should_not match('/hello/=') }
494
+ end
495
+
496
+ pattern "{&a,b,c}" do
497
+ it { should match("&a=~x&b=42&c=_") .capturing a: '~x', b: '42', c: '_' }
498
+ it { should match("&a=~x&b&c=_") .capturing a: '~x', b: nil, c: '_' }
499
+
500
+ it { should_not match("&a=~x&c=_&b=42").capturing a: '~x', b: '42', c: '_' }
501
+
502
+ it { should_not match("&a=~x&b=42") }
503
+ it { should_not match("a=~x&b=42") }
504
+ it { should_not match("&a=~x&b=#42&c") }
505
+ it { should_not match("&a=~x,b=42,c=_") }
506
+ it { should_not match("~x&b=42&c=_") }
507
+ end
508
+ end
509
+ end
510
+
511
+ context 'level 4' do
512
+ context 'without operator' do
513
+ context 'prefix' do
514
+ pattern '{a:3}/bar' do
515
+ it { should match('foo/bar') .capturing a: 'foo' }
516
+ it { should match('fo/bar') .capturing a: 'fo' }
517
+ it { should match('f/bar') .capturing a: 'f' }
518
+ it { should_not match('fooo/bar') }
519
+ end
520
+
521
+ pattern '{a:3}{b}' do
522
+ it { should match('foobar') .capturing a: 'foo', b: 'bar' }
523
+ end
524
+ end
525
+
526
+ context 'expand' do
527
+ pattern '{a*}' do
528
+ it { should match('a') .capturing a: 'a' }
529
+ it { should match('a,b') .capturing a: 'a,b' }
530
+ it { should match('a,b,c') .capturing a: 'a,b,c' }
531
+ it { should_not match('a,b/c') }
532
+ it { should_not match('a,') }
533
+
534
+ example { pattern.params('a').should be == { 'a' => ['a'] }}
535
+ example { pattern.params('a,b').should be == { 'a' => ['a', 'b'] }}
536
+ end
537
+
538
+ pattern '{a*},{b}' do
539
+ it { should match('a,b') .capturing a: 'a', b: 'b' }
540
+ it { should match('a,b,c') .capturing a: 'a,b', b: 'c' }
541
+ it { should_not match('a,b/c') }
542
+ it { should_not match('a,') }
543
+
544
+ example { pattern.params('a,b').should be == { 'a' => ['a'], 'b' => 'b' }}
545
+ example { pattern.params('a,b,c').should be == { 'a' => ['a', 'b'], 'b' => 'c' }}
546
+ end
547
+
548
+ pattern '{a*,b}' do
549
+ it { should match('a,b') .capturing a: 'a', b: 'b' }
550
+ it { should match('a,b,c') .capturing a: 'a,b', b: 'c' }
551
+ it { should_not match('a,b/c') }
552
+ it { should_not match('a,') }
553
+
554
+ example { pattern.params('a,b').should be == { 'a' => ['a'], 'b' => 'b' }}
555
+ example { pattern.params('a,b,c').should be == { 'a' => ['a', 'b'], 'b' => 'c' }}
556
+ end
557
+ end
558
+ end
559
+
560
+ context 'operator +' do
561
+ context 'prefix' do
562
+ pattern '{+a:3}/bar' do
563
+ it { should match('foo/bar') .capturing a: 'foo' }
564
+ it { should match('fo/bar') .capturing a: 'fo' }
565
+ it { should match('f/bar') .capturing a: 'f' }
566
+ it { should_not match('fooo/bar') }
567
+ end
568
+
569
+ pattern '{+a:3}{b}' do
570
+ it { should match('foobar') .capturing a: 'foo', b: 'bar' }
571
+ end
572
+ end
573
+
574
+ context 'expand' do
575
+ pattern '{+a*}' do
576
+ it { should match('a') .capturing a: 'a' }
577
+ it { should match('a,b') .capturing a: 'a,b' }
578
+ it { should match('a,b,c') .capturing a: 'a,b,c' }
579
+ it { should match('a,b/c') .capturing a: 'a,b/c' }
580
+ end
581
+
582
+ pattern '{+a*},{b}' do
583
+ it { should match('a,b') .capturing a: 'a', b: 'b' }
584
+ it { should match('a,b,c') .capturing a: 'a,b', b: 'c' }
585
+ it { should_not match('a,b/c') }
586
+ it { should_not match('a,') }
587
+
588
+ example { pattern.params('a,b').should be == { 'a' => ['a'], 'b' => 'b' }}
589
+ example { pattern.params('a,b,c').should be == { 'a' => ['a', 'b'], 'b' => 'c' }}
590
+ end
591
+ end
592
+ end
593
+
594
+ context 'operator #' do
595
+ context 'prefix' do
596
+ pattern '{#a:3}/bar' do
597
+ it { should match('#foo/bar') .capturing a: 'foo' }
598
+ it { should match('#fo/bar') .capturing a: 'fo' }
599
+ it { should match('#f/bar') .capturing a: 'f' }
600
+ it { should_not match('#fooo/bar') }
601
+ end
602
+
603
+ pattern '{#a:3}{b}' do
604
+ it { should match('#foobar') .capturing a: 'foo', b: 'bar' }
605
+ end
606
+ end
607
+
608
+ context 'expand' do
609
+ pattern '{#a*}' do
610
+ it { should match('#a') .capturing a: 'a' }
611
+ it { should match('#a,b') .capturing a: 'a,b' }
612
+ it { should match('#a,b,c') .capturing a: 'a,b,c' }
613
+ it { should match('#a,b/c') .capturing a: 'a,b/c' }
614
+
615
+ example { pattern.params('#a,b').should be == { 'a' => ['a', 'b'] }}
616
+ example { pattern.params('#a,b,c').should be == { 'a' => ['a', 'b', 'c'] }}
617
+ end
618
+
619
+ pattern '{#a*,b}' do
620
+ it { should match('#a,b') .capturing a: 'a', b: 'b' }
621
+ it { should match('#a,b,c') .capturing a: 'a,b', b: 'c' }
622
+ it { should_not match('#a,') }
623
+
624
+ example { pattern.params('#a,b').should be == { 'a' => ['a'], 'b' => 'b' }}
625
+ example { pattern.params('#a,b,c').should be == { 'a' => ['a', 'b'], 'b' => 'c' }}
626
+ end
627
+ end
628
+ end
629
+
630
+ context 'operator .' do
631
+ context 'prefix' do
632
+ pattern '{.a:3}/bar' do
633
+ it { should match('.foo/bar') .capturing a: 'foo' }
634
+ it { should match('.fo/bar') .capturing a: 'fo' }
635
+ it { should match('.f/bar') .capturing a: 'f' }
636
+ it { should_not match('.fooo/bar') }
637
+ end
638
+
639
+ pattern '{.a:3}{b}' do
640
+ it { should match('.foobar') .capturing a: 'foo', b: 'bar' }
641
+ end
642
+ end
643
+
644
+ context 'expand' do
645
+ pattern '{.a*}' do
646
+ it { should match('.a') .capturing a: 'a' }
647
+ it { should match('.a.b') .capturing a: 'a.b' }
648
+ it { should match('.a.b.c') .capturing a: 'a.b.c' }
649
+ it { should_not match('.a.b,c') }
650
+ it { should_not match('.a,') }
651
+ end
652
+
653
+ pattern '{.a*,b}' do
654
+ it { should match('.a.b') .capturing a: 'a', b: 'b' }
655
+ it { should match('.a.b.c') .capturing a: 'a.b', b: 'c' }
656
+ it { should_not match('.a.b/c') }
657
+ it { should_not match('.a.') }
658
+
659
+ example { pattern.params('.a.b').should be == { 'a' => ['a'], 'b' => 'b' }}
660
+ example { pattern.params('.a.b.c').should be == { 'a' => ['a', 'b'], 'b' => 'c' }}
661
+ end
662
+ end
663
+ end
664
+
665
+ context 'operator /' do
666
+ context 'prefix' do
667
+ pattern '{/a:3}/bar' do
668
+ it { should match('/foo/bar') .capturing a: 'foo' }
669
+ it { should match('/fo/bar') .capturing a: 'fo' }
670
+ it { should match('/f/bar') .capturing a: 'f' }
671
+ it { should_not match('/fooo/bar') }
672
+ end
673
+
674
+ pattern '{/a:3}{b}' do
675
+ it { should match('/foobar') .capturing a: 'foo', b: 'bar' }
676
+ end
677
+ end
678
+
679
+ context 'expand' do
680
+ pattern '{/a*}' do
681
+ it { should match('/a') .capturing a: 'a' }
682
+ it { should match('/a/b') .capturing a: 'a/b' }
683
+ it { should match('/a/b/c') .capturing a: 'a/b/c' }
684
+ it { should_not match('/a/b,c') }
685
+ it { should_not match('/a,') }
686
+ end
687
+
688
+ pattern '{/a*,b}' do
689
+ it { should match('/a/b') .capturing a: 'a', b: 'b' }
690
+ it { should match('/a/b/c') .capturing a: 'a/b', b: 'c' }
691
+ it { should_not match('/a/b,c') }
692
+ it { should_not match('/a/') }
693
+
694
+ example { pattern.params('/a/b').should be == { 'a' => ['a'], 'b' => 'b' }}
695
+ example { pattern.params('/a/b/c').should be == { 'a' => ['a', 'b'], 'b' => 'c' }}
696
+ end
697
+ end
698
+ end
699
+
700
+ context 'operator ;' do
701
+ context 'prefix' do
702
+ pattern '{;a:3}/bar' do
703
+ it { should match(';a=foo/bar') .capturing a: 'foo' }
704
+ it { should match(';a=fo/bar') .capturing a: 'fo' }
705
+ it { should match(';a=f/bar') .capturing a: 'f' }
706
+ it { should_not match(';a=fooo/bar') }
707
+ end
708
+
709
+ pattern '{;a:3}{b}' do
710
+ it { should match(';a=foobar') .capturing a: 'foo', b: 'bar' }
711
+ end
712
+ end
713
+
714
+ context 'expand' do
715
+ pattern '{;a*}' do
716
+ it { should match(';a=1') .capturing a: 'a=1' }
717
+ it { should match(';a=1;a=2') .capturing a: 'a=1;a=2' }
718
+ it { should match(';a=1;a=2;a=3') .capturing a: 'a=1;a=2;a=3' }
719
+ it { should_not match(';a=1;a=2;b=3') }
720
+ it { should_not match(';a=1;a=2;a=3,') }
721
+ end
722
+
723
+ pattern '{;a*,b}' do
724
+ it { should match(';a=1;b') .capturing a: 'a=1', b: nil }
725
+ it { should match(';a=2;a=2;b=1') .capturing a: 'a=2;a=2', b: '1' }
726
+ it { should_not match(';a;b;c') }
727
+ it { should_not match(';a;') }
728
+
729
+ example { pattern.params(';a=2;a=2;b').should be == { 'a' => ['2', '2'], 'b' => nil }}
730
+ end
731
+ end
732
+ end
733
+
734
+ context 'operator ?' do
735
+ context 'prefix' do
736
+ pattern '{?a:3}/bar' do
737
+ it { should match('?a=foo/bar') .capturing a: 'foo' }
738
+ it { should match('?a=fo/bar') .capturing a: 'fo' }
739
+ it { should match('?a=f/bar') .capturing a: 'f' }
740
+ it { should_not match('?a=fooo/bar') }
741
+ end
742
+
743
+ pattern '{?a:3}{b}' do
744
+ it { should match('?a=foobar') .capturing a: 'foo', b: 'bar' }
745
+ end
746
+ end
747
+
748
+ context 'expand' do
749
+ pattern '{?a*}' do
750
+ it { should match('?a=1') .capturing a: 'a=1' }
751
+ it { should match('?a=1&a=2') .capturing a: 'a=1&a=2' }
752
+ it { should match('?a=1&a=2&a=3') .capturing a: 'a=1&a=2&a=3' }
753
+ it { should_not match('?a=1&a=2&b=3') }
754
+ it { should_not match('?a=1&a=2&a=3,') }
755
+ end
756
+
757
+ pattern '{?a*,b}' do
758
+ it { should match('?a=1&b') .capturing a: 'a=1', b: nil }
759
+ it { should match('?a=2&a=2&b=1') .capturing a: 'a=2&a=2', b: '1' }
760
+ it { should_not match('?a&b&c') }
761
+ it { should_not match('?a&') }
762
+
763
+ example { pattern.params('?a=2&a=2&b').should be == { 'a' => ['2', '2'], 'b' => nil }}
764
+ end
765
+ end
766
+ end
767
+
768
+ context 'operator &' do
769
+ context 'prefix' do
770
+ pattern '{&a:3}/bar' do
771
+ it { should match('&a=foo/bar') .capturing a: 'foo' }
772
+ it { should match('&a=fo/bar') .capturing a: 'fo' }
773
+ it { should match('&a=f/bar') .capturing a: 'f' }
774
+ it { should_not match('&a=fooo/bar') }
775
+ end
776
+
777
+ pattern '{&a:3}{b}' do
778
+ it { should match('&a=foobar') .capturing a: 'foo', b: 'bar' }
779
+ end
780
+ end
781
+
782
+ context 'expand' do
783
+ pattern '{&a*}' do
784
+ it { should match('&a=1') .capturing a: 'a=1' }
785
+ it { should match('&a=1&a=2') .capturing a: 'a=1&a=2' }
786
+ it { should match('&a=1&a=2&a=3') .capturing a: 'a=1&a=2&a=3' }
787
+ it { should_not match('&a=1&a=2&b=3') }
788
+ it { should_not match('&a=1&a=2&a=3,') }
789
+ end
790
+
791
+ pattern '{&a*,b}' do
792
+ it { should match('&a=1&b') .capturing a: 'a=1', b: nil }
793
+ it { should match('&a=2&a=2&b=1') .capturing a: 'a=2&a=2', b: '1' }
794
+ it { should_not match('&a&b&c') }
795
+ it { should_not match('&a&') }
796
+
797
+ example { pattern.params('&a=2&a=2&b').should be == { 'a' => ['2', '2'], 'b' => nil }}
798
+ example { pattern.params('&a=2&a=%20&b').should be == { 'a' => ['2', ' '], 'b' => nil }}
799
+ end
800
+ end
801
+ end
802
+ end
803
+
804
+ context 'invalid syntax' do
805
+ example 'unexpected closing bracket' do
806
+ expect { Mustermann::Template.new('foo}bar') }.
807
+ to raise_error(Mustermann::ParseError, 'unexpected } while parsing "foo}bar"')
808
+ end
809
+
810
+ example 'missing closing bracket' do
811
+ expect { Mustermann::Template.new('foo{bar') }.
812
+ to raise_error(Mustermann::ParseError, 'unexpected end of string while parsing "foo{bar"')
813
+ end
814
+ end
815
+ end