mustermann 1.0.1 → 1.0.2.rc1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +18 -0
  3. data/.rspec +5 -0
  4. data/.travis.yml +25 -0
  5. data/.yardopts +3 -0
  6. data/Gemfile +7 -0
  7. data/README.md +230 -799
  8. data/Rakefile +27 -0
  9. data/mustermann-contrib/LICENSE +23 -0
  10. data/mustermann-contrib/README.md +1155 -0
  11. data/mustermann-contrib/examples/highlighting.rb +35 -0
  12. data/mustermann-contrib/highlighting.png +0 -0
  13. data/mustermann-contrib/irb.png +0 -0
  14. data/mustermann-contrib/lib/mustermann/cake.rb +19 -0
  15. data/mustermann-contrib/lib/mustermann/express.rb +38 -0
  16. data/mustermann-contrib/lib/mustermann/file_utils.rb +218 -0
  17. data/mustermann-contrib/lib/mustermann/file_utils/glob_pattern.rb +40 -0
  18. data/mustermann-contrib/lib/mustermann/fileutils.rb +1 -0
  19. data/mustermann-contrib/lib/mustermann/flask.rb +199 -0
  20. data/mustermann-contrib/lib/mustermann/pyramid.rb +29 -0
  21. data/mustermann-contrib/lib/mustermann/rails.rb +47 -0
  22. data/mustermann-contrib/lib/mustermann/shell.rb +57 -0
  23. data/mustermann-contrib/lib/mustermann/simple.rb +51 -0
  24. data/mustermann-contrib/lib/mustermann/string_scanner.rb +314 -0
  25. data/mustermann-contrib/lib/mustermann/strscan.rb +1 -0
  26. data/mustermann-contrib/lib/mustermann/template.rb +63 -0
  27. data/mustermann-contrib/lib/mustermann/uri_template.rb +1 -0
  28. data/mustermann-contrib/lib/mustermann/versions.rb +47 -0
  29. data/mustermann-contrib/lib/mustermann/visualizer.rb +39 -0
  30. data/mustermann-contrib/lib/mustermann/visualizer/highlight.rb +138 -0
  31. data/mustermann-contrib/lib/mustermann/visualizer/highlighter.rb +38 -0
  32. data/mustermann-contrib/lib/mustermann/visualizer/highlighter/ad_hoc.rb +95 -0
  33. data/mustermann-contrib/lib/mustermann/visualizer/highlighter/ast.rb +103 -0
  34. data/mustermann-contrib/lib/mustermann/visualizer/highlighter/composite.rb +46 -0
  35. data/mustermann-contrib/lib/mustermann/visualizer/highlighter/dummy.rb +19 -0
  36. data/mustermann-contrib/lib/mustermann/visualizer/highlighter/regular.rb +105 -0
  37. data/mustermann-contrib/lib/mustermann/visualizer/pattern_extension.rb +69 -0
  38. data/mustermann-contrib/lib/mustermann/visualizer/renderer/ansi.rb +24 -0
  39. data/mustermann-contrib/lib/mustermann/visualizer/renderer/generic.rb +47 -0
  40. data/mustermann-contrib/lib/mustermann/visualizer/renderer/hansi_template.rb +35 -0
  41. data/mustermann-contrib/lib/mustermann/visualizer/renderer/html.rb +51 -0
  42. data/mustermann-contrib/lib/mustermann/visualizer/renderer/sexp.rb +38 -0
  43. data/mustermann-contrib/lib/mustermann/visualizer/tree.rb +64 -0
  44. data/mustermann-contrib/lib/mustermann/visualizer/tree_renderer.rb +79 -0
  45. data/mustermann-contrib/mustermann-contrib.gemspec +19 -0
  46. data/mustermann-contrib/spec/cake_spec.rb +91 -0
  47. data/mustermann-contrib/spec/express_spec.rb +210 -0
  48. data/mustermann-contrib/spec/file_utils_spec.rb +120 -0
  49. data/mustermann-contrib/spec/flask_spec.rb +362 -0
  50. data/mustermann-contrib/spec/flask_subclass_spec.rb +369 -0
  51. data/mustermann-contrib/spec/pattern_extension_spec.rb +50 -0
  52. data/mustermann-contrib/spec/pyramid_spec.rb +102 -0
  53. data/mustermann-contrib/spec/rails_spec.rb +648 -0
  54. data/mustermann-contrib/spec/shell_spec.rb +148 -0
  55. data/mustermann-contrib/spec/simple_spec.rb +269 -0
  56. data/mustermann-contrib/spec/string_scanner_spec.rb +272 -0
  57. data/mustermann-contrib/spec/template_spec.rb +842 -0
  58. data/mustermann-contrib/spec/visualizer_spec.rb +199 -0
  59. data/mustermann-contrib/theme.png +0 -0
  60. data/mustermann-contrib/tree.png +0 -0
  61. data/mustermann/LICENSE +23 -0
  62. data/mustermann/README.md +853 -0
  63. data/{bench → mustermann/bench}/capturing.rb +0 -0
  64. data/{bench → mustermann/bench}/regexp.rb +0 -0
  65. data/{bench → mustermann/bench}/simple_vs_sinatra.rb +0 -0
  66. data/{bench → mustermann/bench}/template_vs_addressable.rb +0 -0
  67. data/{lib → mustermann/lib}/mustermann.rb +0 -0
  68. data/{lib → mustermann/lib}/mustermann/ast/boundaries.rb +0 -0
  69. data/{lib → mustermann/lib}/mustermann/ast/compiler.rb +0 -0
  70. data/{lib → mustermann/lib}/mustermann/ast/expander.rb +0 -0
  71. data/{lib → mustermann/lib}/mustermann/ast/node.rb +0 -0
  72. data/{lib → mustermann/lib}/mustermann/ast/param_scanner.rb +0 -0
  73. data/{lib → mustermann/lib}/mustermann/ast/parser.rb +0 -0
  74. data/{lib → mustermann/lib}/mustermann/ast/pattern.rb +0 -0
  75. data/{lib → mustermann/lib}/mustermann/ast/template_generator.rb +0 -0
  76. data/{lib → mustermann/lib}/mustermann/ast/transformer.rb +0 -0
  77. data/{lib → mustermann/lib}/mustermann/ast/translator.rb +0 -0
  78. data/{lib → mustermann/lib}/mustermann/ast/validation.rb +0 -0
  79. data/{lib → mustermann/lib}/mustermann/caster.rb +0 -0
  80. data/{lib → mustermann/lib}/mustermann/composite.rb +0 -0
  81. data/{lib → mustermann/lib}/mustermann/concat.rb +13 -2
  82. data/{lib → mustermann/lib}/mustermann/equality_map.rb +0 -0
  83. data/{lib → mustermann/lib}/mustermann/error.rb +0 -0
  84. data/{lib → mustermann/lib}/mustermann/expander.rb +0 -0
  85. data/{lib → mustermann/lib}/mustermann/extension.rb +0 -0
  86. data/{lib → mustermann/lib}/mustermann/identity.rb +0 -0
  87. data/{lib → mustermann/lib}/mustermann/mapper.rb +0 -0
  88. data/{lib → mustermann/lib}/mustermann/pattern.rb +1 -1
  89. data/{lib → mustermann/lib}/mustermann/pattern_cache.rb +0 -0
  90. data/{lib → mustermann/lib}/mustermann/regexp.rb +0 -0
  91. data/{lib → mustermann/lib}/mustermann/regexp_based.rb +0 -0
  92. data/{lib → mustermann/lib}/mustermann/regular.rb +0 -0
  93. data/{lib → mustermann/lib}/mustermann/simple_match.rb +0 -0
  94. data/{lib → mustermann/lib}/mustermann/sinatra.rb +1 -1
  95. data/{lib → mustermann/lib}/mustermann/sinatra/parser.rb +0 -0
  96. data/{lib → mustermann/lib}/mustermann/sinatra/safe_renderer.rb +0 -0
  97. data/{lib → mustermann/lib}/mustermann/sinatra/try_convert.rb +0 -0
  98. data/{lib → mustermann/lib}/mustermann/to_pattern.rb +0 -0
  99. data/{lib → mustermann/lib}/mustermann/version.rb +1 -1
  100. data/{mustermann.gemspec → mustermann/mustermann.gemspec} +0 -0
  101. data/{spec → mustermann/spec}/ast_spec.rb +0 -0
  102. data/{spec → mustermann/spec}/composite_spec.rb +0 -0
  103. data/{spec → mustermann/spec}/concat_spec.rb +12 -0
  104. data/{spec → mustermann/spec}/equality_map_spec.rb +0 -0
  105. data/{spec → mustermann/spec}/expander_spec.rb +0 -0
  106. data/{spec → mustermann/spec}/extension_spec.rb +0 -0
  107. data/{spec → mustermann/spec}/identity_spec.rb +0 -0
  108. data/{spec → mustermann/spec}/mapper_spec.rb +0 -0
  109. data/{spec → mustermann/spec}/mustermann_spec.rb +0 -0
  110. data/{spec → mustermann/spec}/pattern_spec.rb +0 -0
  111. data/{spec → mustermann/spec}/regexp_based_spec.rb +0 -0
  112. data/{spec → mustermann/spec}/regular_spec.rb +0 -0
  113. data/{spec → mustermann/spec}/simple_match_spec.rb +0 -0
  114. data/{spec → mustermann/spec}/sinatra_spec.rb +0 -0
  115. data/{spec → mustermann/spec}/to_pattern_spec.rb +0 -0
  116. data/support/lib/support.rb +7 -0
  117. data/support/lib/support/coverage.rb +23 -0
  118. data/support/lib/support/env.rb +19 -0
  119. data/support/lib/support/expand_matcher.rb +28 -0
  120. data/support/lib/support/generate_template_matcher.rb +27 -0
  121. data/support/lib/support/match_matcher.rb +39 -0
  122. data/support/lib/support/pattern.rb +42 -0
  123. data/support/lib/support/projects.rb +20 -0
  124. data/support/lib/support/scan_matcher.rb +63 -0
  125. data/support/support.gemspec +27 -0
  126. metadata +128 -58
@@ -0,0 +1,842 @@
1
+ # frozen_string_literal: true
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
+
12
+ it { should respond_to(:expand) }
13
+ it { should respond_to(:to_templates) }
14
+ end
15
+
16
+ pattern '/' do
17
+ it { should match('/') }
18
+ it { should_not match('/foo') }
19
+ end
20
+
21
+ pattern '/foo' do
22
+ it { should match('/foo') }
23
+ it { should_not match('/bar') }
24
+ it { should_not match('/foo.bar') }
25
+ end
26
+
27
+ pattern '/foo/bar' do
28
+ it { should match('/foo/bar') }
29
+ it { should_not match('/foo%2Fbar') }
30
+ it { should_not match('/foo%2fbar') }
31
+ end
32
+
33
+ pattern '/:foo' do
34
+ it { should match('/:foo') }
35
+ it { should match('/%3Afoo') }
36
+ it { should_not match('/foo') }
37
+ it { should_not match('/foo?') }
38
+ it { should_not match('/foo/bar') }
39
+ it { should_not match('/') }
40
+ it { should_not match('/foo/') }
41
+ end
42
+
43
+ pattern '/föö' do
44
+ it { should match("/f%C3%B6%C3%B6") }
45
+ end
46
+
47
+ pattern '/test$/' do
48
+ it { should match('/test$/') }
49
+ end
50
+
51
+ pattern '/te+st/' do
52
+ it { should match('/te+st/') }
53
+ it { should_not match('/test/') }
54
+ it { should_not match('/teest/') }
55
+ end
56
+
57
+ pattern "/path with spaces" do
58
+ it { should match('/path%20with%20spaces') }
59
+ it { should match('/path%2Bwith%2Bspaces') }
60
+ it { should match('/path+with+spaces') }
61
+ end
62
+
63
+ pattern '/foo&bar' do
64
+ it { should match('/foo&bar') }
65
+ end
66
+
67
+ pattern '/test.bar' do
68
+ it { should match('/test.bar') }
69
+ it { should_not match('/test0bar') }
70
+ end
71
+
72
+ pattern "/path with spaces", space_matches_plus: false do
73
+ it { should match('/path%20with%20spaces') }
74
+ it { should_not match('/path%2Bwith%2Bspaces') }
75
+ it { should_not match('/path+with+spaces') }
76
+ end
77
+
78
+ pattern "/path with spaces", uri_decode: false do
79
+ it { should_not match('/path%20with%20spaces') }
80
+ it { should_not match('/path%2Bwith%2Bspaces') }
81
+ it { should_not match('/path+with+spaces') }
82
+ end
83
+
84
+ context 'level 1' do
85
+ context 'without operator' do
86
+ pattern '/hello/{person}' do
87
+ it { should match('/hello/Frank').capturing person: 'Frank' }
88
+ it { should match('/hello/a_b~c').capturing person: 'a_b~c' }
89
+ it { should match('/hello/a.%20').capturing person: 'a.%20' }
90
+
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
+ it { should_not match('/hello/,') }
102
+ it { should_not match('/hello/;') }
103
+ it { should_not match('/hello/=') }
104
+
105
+ example { pattern.params('/hello/Frank').should be == {'person' => 'Frank'} }
106
+ end
107
+
108
+ pattern "/{foo}/{bar}" do
109
+ it { should match('/foo/bar') .capturing foo: 'foo', bar: 'bar' }
110
+ it { should match('/foo.bar/bar.foo') .capturing foo: 'foo.bar', bar: 'bar.foo' }
111
+ it { should match('/10.1/te.st') .capturing foo: '10.1', bar: 'te.st' }
112
+ it { should match('/10.1.2/te.st') .capturing foo: '10.1.2', bar: 'te.st' }
113
+
114
+ it { should_not match('/foo%2Fbar') }
115
+ it { should_not match('/foo%2fbar') }
116
+ end
117
+ end
118
+ end
119
+
120
+ context 'level 2' do
121
+ context 'operator +' do
122
+ pattern '/hello/{+person}' do
123
+ it { should match('/hello/Frank') .capturing person: 'Frank' }
124
+ it { should match('/hello/a_b~c') .capturing person: 'a_b~c' }
125
+ it { should match('/hello/a.%20') .capturing person: 'a.%20' }
126
+ it { should match('/hello/a/%20') .capturing person: 'a/%20' }
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
+ it { should match('/hello/,') .capturing person: ?, }
138
+ it { should match('/hello/;') .capturing person: ?; }
139
+ it { should match('/hello/=') .capturing person: ?= }
140
+ end
141
+
142
+ pattern "/{+foo}/{bar}" do
143
+ it { should match('/foo/bar') .capturing foo: 'foo', bar: 'bar' }
144
+ it { should match('/foo.bar/bar.foo') .capturing foo: 'foo.bar', bar: 'bar.foo' }
145
+ it { should match('/foo/bar/bar.foo') .capturing foo: 'foo/bar', bar: 'bar.foo' }
146
+ it { should match('/10.1/te.st') .capturing foo: '10.1', bar: 'te.st' }
147
+ it { should match('/10.1.2/te.st') .capturing foo: '10.1.2', bar: 'te.st' }
148
+
149
+ it { should_not match('/foo%2Fbar') }
150
+ it { should_not match('/foo%2fbar') }
151
+ end
152
+ end
153
+
154
+ context 'operator #' do
155
+ pattern '/hello/{#person}' do
156
+ it { should match('/hello/#Frank') .capturing person: 'Frank' }
157
+ it { should match('/hello/#a_b~c') .capturing person: 'a_b~c' }
158
+ it { should match('/hello/#a.%20') .capturing person: 'a.%20' }
159
+ it { should match('/hello/#a/%20') .capturing person: 'a/%20' }
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
+ it { should match('/hello/#,') .capturing person: ?, }
171
+ it { should match('/hello/#;') .capturing person: ?; }
172
+ it { should match('/hello/#=') .capturing person: ?= }
173
+
174
+
175
+ it { should_not match('/hello/Frank') }
176
+ it { should_not match('/hello/a_b~c') }
177
+ it { should_not match('/hello/a.%20') }
178
+
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
+ it { should_not match('/hello/,') }
190
+ it { should_not match('/hello/;') }
191
+ it { should_not match('/hello/=') }
192
+
193
+
194
+ example { pattern.params('/hello/#Frank').should be == {'person' => 'Frank'} }
195
+ end
196
+
197
+ pattern "/{+foo}/{#bar}" do
198
+ it { should match('/foo/#bar') .capturing foo: 'foo', bar: 'bar' }
199
+ it { should match('/foo.bar/#bar.foo') .capturing foo: 'foo.bar', bar: 'bar.foo' }
200
+ it { should match('/foo/bar/#bar.foo') .capturing foo: 'foo/bar', bar: 'bar.foo' }
201
+ it { should match('/10.1/#te.st') .capturing foo: '10.1', bar: 'te.st' }
202
+ it { should match('/10.1.2/#te.st') .capturing foo: '10.1.2', bar: 'te.st' }
203
+
204
+ it { should_not match('/foo%2F#bar') }
205
+ it { should_not match('/foo%2f#bar') }
206
+
207
+ example { pattern.params('/hello/#Frank').should be == {'foo' => 'hello', 'bar' => 'Frank'} }
208
+ end
209
+ end
210
+ end
211
+
212
+ context 'level 3' do
213
+ context 'without operator' do
214
+ pattern "{a,b,c}" do
215
+ it { should match("~x,42,_").capturing a: '~x', b: '42', c: '_' }
216
+ it { should_not match("~x,42") }
217
+ it { should_not match("~x/42") }
218
+ it { should_not match("~x#42") }
219
+ it { should_not match("~x,42,_#42") }
220
+
221
+ example { pattern.params('d,f,g').should be == {'a' => 'd', 'b' => 'f', 'c' => 'g'} }
222
+ end
223
+ end
224
+
225
+ context 'operator +' do
226
+ pattern "{+a,b,c}" do
227
+ it { should match("~x,42,_") .capturing a: '~x', b: '42', c: '_' }
228
+ it { should match("~x,42,_#42") .capturing a: '~x', b: '42', c: '_#42' }
229
+ it { should match("~/x,42,_/42") .capturing a: '~/x', b: '42', c: '_/42' }
230
+
231
+ it { should_not match("~x,42") }
232
+ it { should_not match("~x/42") }
233
+ it { should_not match("~x#42") }
234
+ end
235
+ end
236
+
237
+ context 'operator #' do
238
+ pattern "{#a,b,c}" do
239
+ it { should match("#~x,42,_") .capturing a: '~x', b: '42', c: '_' }
240
+ it { should match("#~x,42,_#42") .capturing a: '~x', b: '42', c: '_#42' }
241
+ it { should match("#~/x,42,_#42") .capturing a: '~/x', b: '42', c: '_#42' }
242
+
243
+ it { should_not match("~x,42,_") }
244
+ it { should_not match("~x,42,_#42") }
245
+ it { should_not match("~/x,42,_#42") }
246
+
247
+ it { should_not match("~x,42") }
248
+ it { should_not match("~x/42") }
249
+ it { should_not match("~x#42") }
250
+ end
251
+ end
252
+
253
+ context 'operator .' do
254
+ pattern '/hello/{.person}' do
255
+ it { should match('/hello/.Frank') .capturing person: 'Frank' }
256
+ it { should match('/hello/.a_b~c') .capturing person: 'a_b~c' }
257
+
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
+ it { should_not match('/hello/.,') }
269
+ it { should_not match('/hello/.;') }
270
+ it { should_not match('/hello/.=') }
271
+
272
+ it { should_not match('/hello/Frank') }
273
+ it { should_not match('/hello/a_b~c') }
274
+ it { should_not match('/hello/a.%20') }
275
+
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
+ it { should_not match('/hello/,') }
287
+ it { should_not match('/hello/;') }
288
+ it { should_not match('/hello/=') }
289
+ end
290
+
291
+ pattern "{.a,b,c}" do
292
+ it { should match(".~x.42._").capturing a: '~x', b: '42', c: '_' }
293
+ it { should_not match(".~x,42") }
294
+ it { should_not match(".~x/42") }
295
+ it { should_not match(".~x#42") }
296
+ it { should_not match(".~x,42,_") }
297
+ it { should_not match("~x.42._") }
298
+ end
299
+ end
300
+
301
+ context 'operator /' do
302
+ pattern '/hello{/person}' do
303
+ it { should match('/hello/Frank') .capturing person: 'Frank' }
304
+ it { should match('/hello/a_b~c') .capturing person: 'a_b~c' }
305
+
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
+ it { should_not match('/hello//,') }
317
+ it { should_not match('/hello//;') }
318
+ it { should_not match('/hello//=') }
319
+
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
+ it { should_not match('/hello/,') }
331
+ it { should_not match('/hello/;') }
332
+ it { should_not match('/hello/=') }
333
+ end
334
+
335
+ pattern "{/a,b,c}" do
336
+ it { should match("/~x/42/_").capturing a: '~x', b: '42', c: '_' }
337
+ it { should_not match("/~x,42") }
338
+ it { should_not match("/~x.42") }
339
+ it { should_not match("/~x#42") }
340
+ it { should_not match("/~x,42,_") }
341
+ it { should_not match("~x/42/_") }
342
+ end
343
+ end
344
+
345
+ context 'operator ;' do
346
+ pattern '/hello/{;person}' do
347
+ it { should match('/hello/;person=Frank') .capturing person: 'Frank' }
348
+ it { should match('/hello/;person=a_b~c') .capturing person: 'a_b~c' }
349
+ it { should match('/hello/;person') .capturing person: nil }
350
+
351
+ it { should_not match('/hello/;persona=Frank') }
352
+ it { should_not match('/hello/;persona=a_b~c') }
353
+
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
+ it { should_not match('/hello/;person=,') }
365
+ it { should_not match('/hello/;person=;') }
366
+ it { should_not match('/hello/;person==') }
367
+
368
+ it { should_not match('/hello/;Frank') }
369
+ it { should_not match('/hello/;a_b~c') }
370
+ it { should_not match('/hello/;a.%20') }
371
+
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
+ it { should_not match('/hello/,') }
383
+ it { should_not match('/hello/;') }
384
+ it { should_not match('/hello/=') }
385
+ end
386
+
387
+ pattern "{;a,b,c}" do
388
+ it { should match(";a=~x;b=42;c=_") .capturing a: '~x', b: '42', c: '_' }
389
+ it { should match(";a=~x;b;c=_") .capturing a: '~x', b: nil, c: '_' }
390
+
391
+ it { should_not match(";a=~x;c=_;b=42").capturing a: '~x', b: '42', c: '_' }
392
+
393
+ it { should_not match(";a=~x;b=42") }
394
+ it { should_not match("a=~x;b=42") }
395
+ it { should_not match(";a=~x;b=#42;c") }
396
+ it { should_not match(";a=~x,b=42,c=_") }
397
+ it { should_not match("~x;b=42;c=_") }
398
+ end
399
+ end
400
+
401
+ context 'operator ?' do
402
+ pattern '/hello/{?person}' do
403
+ it { should match('/hello/?person=Frank') .capturing person: 'Frank' }
404
+ it { should match('/hello/?person=a_b~c') .capturing person: 'a_b~c' }
405
+ it { should match('/hello/?person') .capturing person: nil }
406
+
407
+ it { should_not match('/hello/?persona=Frank') }
408
+ it { should_not match('/hello/?persona=a_b~c') }
409
+
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
+ it { should_not match('/hello/?person=,') }
421
+ it { should_not match('/hello/?person=;') }
422
+ it { should_not match('/hello/?person==') }
423
+
424
+ it { should_not match('/hello/?Frank') }
425
+ it { should_not match('/hello/?a_b~c') }
426
+ it { should_not match('/hello/?a.%20') }
427
+
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
+ it { should_not match('/hello/,') }
439
+ it { should_not match('/hello/;') }
440
+ it { should_not match('/hello/=') }
441
+ end
442
+
443
+ pattern "{?a,b,c}" do
444
+ it { should match("?a=~x&b=42&c=_") .capturing a: '~x', b: '42', c: '_' }
445
+ it { should match("?a=~x&b&c=_") .capturing a: '~x', b: nil, c: '_' }
446
+
447
+ it { should_not match("?a=~x&c=_&b=42").capturing a: '~x', b: '42', c: '_' }
448
+
449
+ it { should_not match("?a=~x&b=42") }
450
+ it { should_not match("a=~x&b=42") }
451
+ it { should_not match("?a=~x&b=#42&c") }
452
+ it { should_not match("?a=~x,b=42,c=_") }
453
+ it { should_not match("~x&b=42&c=_") }
454
+ end
455
+ end
456
+
457
+ context 'operator &' do
458
+ pattern '/hello/{&person}' do
459
+ it { should match('/hello/&person=Frank') .capturing person: 'Frank' }
460
+ it { should match('/hello/&person=a_b~c') .capturing person: 'a_b~c' }
461
+ it { should match('/hello/&person') .capturing person: nil }
462
+
463
+ it { should_not match('/hello/&persona=Frank') }
464
+ it { should_not match('/hello/&persona=a_b~c') }
465
+
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
+ it { should_not match('/hello/&person=,') }
477
+ it { should_not match('/hello/&person=;') }
478
+ it { should_not match('/hello/&person==') }
479
+
480
+ it { should_not match('/hello/&Frank') }
481
+ it { should_not match('/hello/&a_b~c') }
482
+ it { should_not match('/hello/&a.%20') }
483
+
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
+ it { should_not match('/hello/,') }
495
+ it { should_not match('/hello/;') }
496
+ it { should_not match('/hello/=') }
497
+ end
498
+
499
+ pattern "{&a,b,c}" do
500
+ it { should match("&a=~x&b=42&c=_") .capturing a: '~x', b: '42', c: '_' }
501
+ it { should match("&a=~x&b&c=_") .capturing a: '~x', b: nil, c: '_' }
502
+
503
+ it { should_not match("&a=~x&c=_&b=42").capturing a: '~x', b: '42', c: '_' }
504
+
505
+ it { should_not match("&a=~x&b=42") }
506
+ it { should_not match("a=~x&b=42") }
507
+ it { should_not match("&a=~x&b=#42&c") }
508
+ it { should_not match("&a=~x,b=42,c=_") }
509
+ it { should_not match("~x&b=42&c=_") }
510
+ end
511
+ end
512
+ end
513
+
514
+ context 'level 4' do
515
+ context 'without operator' do
516
+ context 'prefix' do
517
+ pattern '{a:3}/bar' do
518
+ it { should match('foo/bar') .capturing a: 'foo' }
519
+ it { should match('fo/bar') .capturing a: 'fo' }
520
+ it { should match('f/bar') .capturing a: 'f' }
521
+ it { should_not match('fooo/bar') }
522
+ end
523
+
524
+ pattern '{a:3}{b}' do
525
+ it { should match('foobar') .capturing a: 'foo', b: 'bar' }
526
+ end
527
+ end
528
+
529
+ context 'expand' do
530
+ pattern '{a*}' do
531
+ it { should match('a') .capturing a: 'a' }
532
+ it { should match('a,b') .capturing a: 'a,b' }
533
+ it { should match('a,b,c') .capturing a: 'a,b,c' }
534
+ it { should_not match('a,b/c') }
535
+ it { should_not match('a,') }
536
+
537
+ example { pattern.params('a').should be == { 'a' => ['a'] }}
538
+ example { pattern.params('a,b').should be == { 'a' => ['a', 'b'] }}
539
+ end
540
+
541
+ pattern '{a*},{b}' do
542
+ it { should match('a,b') .capturing a: 'a', b: 'b' }
543
+ it { should match('a,b,c') .capturing a: 'a,b', b: 'c' }
544
+ it { should_not match('a,b/c') }
545
+ it { should_not match('a,') }
546
+
547
+ example { pattern.params('a,b').should be == { 'a' => ['a'], 'b' => 'b' }}
548
+ example { pattern.params('a,b,c').should be == { 'a' => ['a', 'b'], 'b' => 'c' }}
549
+ end
550
+
551
+ pattern '{a*,b}' do
552
+ it { should match('a,b') .capturing a: 'a', b: 'b' }
553
+ it { should match('a,b,c') .capturing a: 'a,b', b: 'c' }
554
+ it { should_not match('a,b/c') }
555
+ it { should_not match('a,') }
556
+
557
+ example { pattern.params('a,b').should be == { 'a' => ['a'], 'b' => 'b' }}
558
+ example { pattern.params('a,b,c').should be == { 'a' => ['a', 'b'], 'b' => 'c' }}
559
+ end
560
+ end
561
+ end
562
+
563
+ context 'operator +' do
564
+ pattern '/{a}/{+b}' do
565
+ it { should match('/foo/bar/baz').capturing(a: 'foo', b: 'bar/baz') }
566
+ it { should expand(a: 'foo/bar', b: 'foo/bar').to('/foo%2Fbar/foo/bar') }
567
+ end
568
+
569
+ context 'prefix' do
570
+ pattern '{+a:3}/bar' do
571
+ it { should match('foo/bar') .capturing a: 'foo' }
572
+ it { should match('fo/bar') .capturing a: 'fo' }
573
+ it { should match('f/bar') .capturing a: 'f' }
574
+ it { should_not match('fooo/bar') }
575
+ end
576
+
577
+ pattern '{+a:3}{b}' do
578
+ it { should match('foobar') .capturing a: 'foo', b: 'bar' }
579
+ end
580
+ end
581
+
582
+ context 'expand' do
583
+ pattern '{+a*}' do
584
+ it { should match('a') .capturing a: 'a' }
585
+ it { should match('a,b') .capturing a: 'a,b' }
586
+ it { should match('a,b,c') .capturing a: 'a,b,c' }
587
+ it { should match('a,b/c') .capturing a: 'a,b/c' }
588
+ end
589
+
590
+ pattern '{+a*},{b}' do
591
+ it { should match('a,b') .capturing a: 'a', b: 'b' }
592
+ it { should match('a,b,c') .capturing a: 'a,b', b: 'c' }
593
+ it { should_not match('a,b/c') }
594
+ it { should_not match('a,') }
595
+
596
+ example { pattern.params('a,b').should be == { 'a' => ['a'], 'b' => 'b' }}
597
+ example { pattern.params('a,b,c').should be == { 'a' => ['a', 'b'], 'b' => 'c' }}
598
+ end
599
+ end
600
+ end
601
+
602
+ context 'operator #' do
603
+ context 'prefix' do
604
+ pattern '{#a:3}/bar' do
605
+ it { should match('#foo/bar') .capturing a: 'foo' }
606
+ it { should match('#fo/bar') .capturing a: 'fo' }
607
+ it { should match('#f/bar') .capturing a: 'f' }
608
+ it { should_not match('#fooo/bar') }
609
+ end
610
+
611
+ pattern '{#a:3}{b}' do
612
+ it { should match('#foobar') .capturing a: 'foo', b: 'bar' }
613
+ end
614
+ end
615
+
616
+ context 'expand' do
617
+ pattern '{#a*}' do
618
+ it { should match('#a') .capturing a: 'a' }
619
+ it { should match('#a,b') .capturing a: 'a,b' }
620
+ it { should match('#a,b,c') .capturing a: 'a,b,c' }
621
+ it { should match('#a,b/c') .capturing a: 'a,b/c' }
622
+
623
+ example { pattern.params('#a,b').should be == { 'a' => ['a', 'b'] }}
624
+ example { pattern.params('#a,b,c').should be == { 'a' => ['a', 'b', 'c'] }}
625
+ end
626
+
627
+ pattern '{#a*,b}' do
628
+ it { should match('#a,b') .capturing a: 'a', b: 'b' }
629
+ it { should match('#a,b,c') .capturing a: 'a,b', b: 'c' }
630
+ it { should_not match('#a,') }
631
+
632
+ example { pattern.params('#a,b').should be == { 'a' => ['a'], 'b' => 'b' }}
633
+ example { pattern.params('#a,b,c').should be == { 'a' => ['a', 'b'], 'b' => 'c' }}
634
+ end
635
+ end
636
+ end
637
+
638
+ context 'operator .' do
639
+ context 'prefix' do
640
+ pattern '{.a:3}/bar' do
641
+ it { should match('.foo/bar') .capturing a: 'foo' }
642
+ it { should match('.fo/bar') .capturing a: 'fo' }
643
+ it { should match('.f/bar') .capturing a: 'f' }
644
+ it { should_not match('.fooo/bar') }
645
+ end
646
+
647
+ pattern '{.a:3}{b}' do
648
+ it { should match('.foobar') .capturing a: 'foo', b: 'bar' }
649
+ end
650
+ end
651
+
652
+ context 'expand' do
653
+ pattern '{.a*}' do
654
+ it { should match('.a') .capturing a: 'a' }
655
+ it { should match('.a.b') .capturing a: 'a.b' }
656
+ it { should match('.a.b.c') .capturing a: 'a.b.c' }
657
+ it { should_not match('.a.b,c') }
658
+ it { should_not match('.a,') }
659
+ end
660
+
661
+ pattern '{.a*,b}' do
662
+ it { should match('.a.b') .capturing a: 'a', b: 'b' }
663
+ it { should match('.a.b.c') .capturing a: 'a.b', b: 'c' }
664
+ it { should_not match('.a.b/c') }
665
+ it { should_not match('.a.') }
666
+
667
+ example { pattern.params('.a.b').should be == { 'a' => ['a'], 'b' => 'b' }}
668
+ example { pattern.params('.a.b.c').should be == { 'a' => ['a', 'b'], 'b' => 'c' }}
669
+ end
670
+ end
671
+ end
672
+
673
+ context 'operator /' do
674
+ context 'prefix' do
675
+ pattern '{/a:3}/bar' do
676
+ it { should match('/foo/bar') .capturing a: 'foo' }
677
+ it { should match('/fo/bar') .capturing a: 'fo' }
678
+ it { should match('/f/bar') .capturing a: 'f' }
679
+ it { should_not match('/fooo/bar') }
680
+ end
681
+
682
+ pattern '{/a:3}{b}' do
683
+ it { should match('/foobar') .capturing a: 'foo', b: 'bar' }
684
+ end
685
+ end
686
+
687
+ context 'expand' do
688
+ pattern '{/a*}' do
689
+ it { should match('/a') .capturing a: 'a' }
690
+ it { should match('/a/b') .capturing a: 'a/b' }
691
+ it { should match('/a/b/c') .capturing a: 'a/b/c' }
692
+ it { should_not match('/a/b,c') }
693
+ it { should_not match('/a,') }
694
+ end
695
+
696
+ pattern '{/a*,b}' do
697
+ it { should match('/a/b') .capturing a: 'a', b: 'b' }
698
+ it { should match('/a/b/c') .capturing a: 'a/b', b: 'c' }
699
+ it { should_not match('/a/b,c') }
700
+ it { should_not match('/a/') }
701
+
702
+ example { pattern.params('/a/b').should be == { 'a' => ['a'], 'b' => 'b' }}
703
+ example { pattern.params('/a/b/c').should be == { 'a' => ['a', 'b'], 'b' => 'c' }}
704
+ end
705
+ end
706
+ end
707
+
708
+ context 'operator ;' do
709
+ context 'prefix' do
710
+ pattern '{;a:3}/bar' do
711
+ it { should match(';a=foo/bar') .capturing a: 'foo' }
712
+ it { should match(';a=fo/bar') .capturing a: 'fo' }
713
+ it { should match(';a=f/bar') .capturing a: 'f' }
714
+ it { should_not match(';a=fooo/bar') }
715
+ end
716
+
717
+ pattern '{;a:3}{b}' do
718
+ it { should match(';a=foobar') .capturing a: 'foo', b: 'bar' }
719
+ end
720
+ end
721
+
722
+ context 'expand' do
723
+ pattern '{;a*}' do
724
+ it { should match(';a=1') .capturing a: 'a=1' }
725
+ it { should match(';a=1;a=2') .capturing a: 'a=1;a=2' }
726
+ it { should match(';a=1;a=2;a=3') .capturing a: 'a=1;a=2;a=3' }
727
+ it { should_not match(';a=1;a=2;b=3') }
728
+ it { should_not match(';a=1;a=2;a=3,') }
729
+ end
730
+
731
+ pattern '{;a*,b}' do
732
+ it { should match(';a=1;b') .capturing a: 'a=1', b: nil }
733
+ it { should match(';a=2;a=2;b=1') .capturing a: 'a=2;a=2', b: '1' }
734
+ it { should_not match(';a;b;c') }
735
+ it { should_not match(';a;') }
736
+
737
+ example { pattern.params(';a=2;a=2;b').should be == { 'a' => ['2', '2'], 'b' => nil }}
738
+ end
739
+ end
740
+ end
741
+
742
+ context 'operator ?' do
743
+ context 'prefix' do
744
+ pattern '{?a:3}/bar' do
745
+ it { should match('?a=foo/bar') .capturing a: 'foo' }
746
+ it { should match('?a=fo/bar') .capturing a: 'fo' }
747
+ it { should match('?a=f/bar') .capturing a: 'f' }
748
+ it { should_not match('?a=fooo/bar') }
749
+ end
750
+
751
+ pattern '{?a:3}{b}' do
752
+ it { should match('?a=foobar') .capturing a: 'foo', b: 'bar' }
753
+ end
754
+ end
755
+
756
+ context 'expand' do
757
+ pattern '{?a*}' do
758
+ it { should match('?a=1') .capturing a: 'a=1' }
759
+ it { should match('?a=1&a=2') .capturing a: 'a=1&a=2' }
760
+ it { should match('?a=1&a=2&a=3') .capturing a: 'a=1&a=2&a=3' }
761
+ it { should_not match('?a=1&a=2&b=3') }
762
+ it { should_not match('?a=1&a=2&a=3,') }
763
+ end
764
+
765
+ pattern '{?a*,b}' do
766
+ it { should match('?a=1&b') .capturing a: 'a=1', b: nil }
767
+ it { should match('?a=2&a=2&b=1') .capturing a: 'a=2&a=2', b: '1' }
768
+ it { should_not match('?a&b&c') }
769
+ it { should_not match('?a&') }
770
+
771
+ example { pattern.params('?a=2&a=2&b').should be == { 'a' => ['2', '2'], 'b' => nil }}
772
+ end
773
+ end
774
+ end
775
+
776
+ context 'operator &' do
777
+ context 'prefix' do
778
+ pattern '{&a:3}/bar' do
779
+ it { should match('&a=foo/bar') .capturing a: 'foo' }
780
+ it { should match('&a=fo/bar') .capturing a: 'fo' }
781
+ it { should match('&a=f/bar') .capturing a: 'f' }
782
+ it { should_not match('&a=fooo/bar') }
783
+ end
784
+
785
+ pattern '{&a:3}{b}' do
786
+ it { should match('&a=foobar') .capturing a: 'foo', b: 'bar' }
787
+ end
788
+ end
789
+
790
+ context 'expand' do
791
+ pattern '{&a*}' do
792
+ it { should match('&a=1') .capturing a: 'a=1' }
793
+ it { should match('&a=1&a=2') .capturing a: 'a=1&a=2' }
794
+ it { should match('&a=1&a=2&a=3') .capturing a: 'a=1&a=2&a=3' }
795
+ it { should_not match('&a=1&a=2&b=3') }
796
+ it { should_not match('&a=1&a=2&a=3,') }
797
+ end
798
+
799
+ pattern '{&a*,b}' do
800
+ it { should match('&a=1&b') .capturing a: 'a=1', b: nil }
801
+ it { should match('&a=2&a=2&b=1') .capturing a: 'a=2&a=2', b: '1' }
802
+ it { should_not match('&a&b&c') }
803
+ it { should_not match('&a&') }
804
+
805
+ example { pattern.params('&a=2&a=2&b').should be == { 'a' => ['2', '2'], 'b' => nil }}
806
+ example { pattern.params('&a=2&a=%20&b').should be == { 'a' => ['2', ' '], 'b' => nil }}
807
+ end
808
+ end
809
+ end
810
+ end
811
+
812
+ context 'invalid syntax' do
813
+ example 'unexpected closing bracket' do
814
+ expect { Mustermann::Template.new('foo}bar') }.
815
+ to raise_error(Mustermann::ParseError, 'unexpected } while parsing "foo}bar"')
816
+ end
817
+
818
+ example 'missing closing bracket' do
819
+ expect { Mustermann::Template.new('foo{bar') }.
820
+ to raise_error(Mustermann::ParseError, 'unexpected end of string while parsing "foo{bar"')
821
+ end
822
+ end
823
+
824
+ context "peeking" do
825
+ subject(:pattern) { Mustermann::Template.new("{name}bar") }
826
+
827
+ describe :peek_size do
828
+ example { pattern.peek_size("foo%20bar/blah") .should be == "foo%20bar".size }
829
+ example { pattern.peek_size("/foo bar") .should be_nil }
830
+ end
831
+
832
+ describe :peek_match do
833
+ example { pattern.peek_match("foo%20bar/blah") .to_s .should be == "foo%20bar" }
834
+ example { pattern.peek_match("/foo bar") .should be_nil }
835
+ end
836
+
837
+ describe :peek_params do
838
+ example { pattern.peek_params("foo%20bar/blah") .should be == [{"name" => "foo "}, "foo%20bar".size] }
839
+ example { pattern.peek_params("/foo bar") .should be_nil }
840
+ end
841
+ end
842
+ end