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,362 @@
1
+ # frozen_string_literal: true
2
+ require 'support'
3
+ require 'mustermann/flask'
4
+
5
+ describe Mustermann::Flask do
6
+ extend Support::Pattern
7
+
8
+ pattern '' do
9
+ it { should match('') }
10
+ it { should_not match('/') }
11
+
12
+ it { should expand.to('') }
13
+ it { should_not expand(a: 1) }
14
+
15
+ it { should generate_template('') }
16
+
17
+ it { should respond_to(:expand) }
18
+ it { should respond_to(:to_templates) }
19
+ end
20
+
21
+ pattern '/' do
22
+ it { should match('/') }
23
+ it { should_not match('/foo') }
24
+
25
+ it { should expand.to('/') }
26
+ it { should_not expand(a: 1) }
27
+ end
28
+
29
+ pattern '/foo' do
30
+ it { should match('/foo') }
31
+ it { should_not match('/bar') }
32
+ it { should_not match('/foo.bar') }
33
+
34
+ it { should expand.to('/foo') }
35
+ it { should_not expand(a: 1) }
36
+ end
37
+
38
+ pattern '/foo/bar' do
39
+ it { should match('/foo/bar') }
40
+ it { should_not match('/foo%2Fbar') }
41
+ it { should_not match('/foo%2fbar') }
42
+
43
+ it { should expand.to('/foo/bar') }
44
+ it { should_not expand(a: 1) }
45
+ end
46
+
47
+ pattern '/<foo>' do
48
+ it { should match('/foo') .capturing foo: 'foo' }
49
+ it { should match('/bar') .capturing foo: 'bar' }
50
+ it { should match('/foo.bar') .capturing foo: 'foo.bar' }
51
+ it { should match('/%0Afoo') .capturing foo: '%0Afoo' }
52
+ it { should match('/foo%2Fbar') .capturing foo: 'foo%2Fbar' }
53
+
54
+ it { should_not match('/foo?') }
55
+ it { should_not match('/foo/bar') }
56
+ it { should_not match('/') }
57
+ it { should_not match('/foo/') }
58
+
59
+ example { pattern.params('/foo') .should be == {"foo" => "foo"} }
60
+ example { pattern.params('/f%20o') .should be == {"foo" => "f o"} }
61
+ example { pattern.params('').should be_nil }
62
+
63
+ it { should expand(foo: 'bar') .to('/bar') }
64
+ it { should expand(foo: 'b r') .to('/b%20r') }
65
+ it { should expand(foo: 'foo/bar') .to('/foo%2Fbar') }
66
+
67
+ it { should_not expand(foo: 'foo', bar: 'bar') }
68
+ it { should_not expand(bar: 'bar') }
69
+ it { should_not expand }
70
+
71
+ it { should generate_template('/{foo}') }
72
+ end
73
+
74
+ pattern '/<string:foo>' do
75
+ it { should match('/foo') .capturing foo: 'foo' }
76
+ it { should match('/bar') .capturing foo: 'bar' }
77
+ it { should match('/foo.bar') .capturing foo: 'foo.bar' }
78
+ it { should match('/%0Afoo') .capturing foo: '%0Afoo' }
79
+ it { should match('/foo%2Fbar') .capturing foo: 'foo%2Fbar' }
80
+
81
+ it { should_not match('/foo?') }
82
+ it { should_not match('/foo/bar') }
83
+ it { should_not match('/') }
84
+ it { should_not match('/foo/') }
85
+
86
+ example { pattern.params('/foo') .should be == {"foo" => "foo"} }
87
+ example { pattern.params('/f%20o') .should be == {"foo" => "f o"} }
88
+ example { pattern.params('').should be_nil }
89
+
90
+ it { should expand(foo: 'bar') .to('/bar') }
91
+ it { should expand(foo: 'b r') .to('/b%20r') }
92
+ it { should expand(foo: 'foo/bar') .to('/foo%2Fbar') }
93
+
94
+ it { should_not expand(foo: 'foo', bar: 'bar') }
95
+ it { should_not expand(bar: 'bar') }
96
+ it { should_not expand }
97
+
98
+ it { should generate_template('/{foo}') }
99
+ end
100
+
101
+ pattern '/<string(minlength=2):foo>' do
102
+ it { should match('/foo') .capturing foo: 'foo' }
103
+ it { should match('/bar') .capturing foo: 'bar' }
104
+ it { should match('/foo.bar') .capturing foo: 'foo.bar' }
105
+ it { should match('/%0Afoo') .capturing foo: '%0Afoo' }
106
+ it { should match('/foo%2Fbar') .capturing foo: 'foo%2Fbar' }
107
+
108
+ it { should_not match('/f') }
109
+ it { should_not match('/foo?') }
110
+ it { should_not match('/foo/bar') }
111
+ it { should_not match('/') }
112
+ it { should_not match('/foo/') }
113
+
114
+ it { should generate_template('/{foo}') }
115
+ end
116
+
117
+ pattern '/<string(maxlength=3):foo>' do
118
+ it { should match('/f') .capturing foo: 'f' }
119
+ it { should match('/fo') .capturing foo: 'fo' }
120
+ it { should match('/foo') .capturing foo: 'foo' }
121
+ it { should match('/bar') .capturing foo: 'bar' }
122
+
123
+ it { should_not match('/fooo') }
124
+ it { should_not match('/foo.bar') }
125
+ it { should_not match('/foo?') }
126
+ it { should_not match('/foo/bar') }
127
+ it { should_not match('/') }
128
+ it { should_not match('/foo/') }
129
+
130
+ it { should generate_template('/{foo}') }
131
+ end
132
+
133
+ pattern '/<string(length=3):foo>' do
134
+ it { should match('/foo') .capturing foo: 'foo' }
135
+ it { should match('/bar') .capturing foo: 'bar' }
136
+
137
+ it { should_not match('/f') }
138
+ it { should_not match('/fo') }
139
+ it { should_not match('/fooo') }
140
+ it { should_not match('/foo.bar') }
141
+ it { should_not match('/foo?') }
142
+ it { should_not match('/foo/bar') }
143
+ it { should_not match('/') }
144
+ it { should_not match('/foo/') }
145
+
146
+ it { should generate_template('/{foo}') }
147
+ end
148
+
149
+ pattern '/<int:foo>' do
150
+ it { should match('/42').capturing foo: '42' }
151
+
152
+ it { should_not match('/1.0') }
153
+ it { should_not match('/.5') }
154
+ it { should_not match('/foo') }
155
+ it { should_not match('/bar') }
156
+ it { should_not match('/foo.bar') }
157
+ it { should_not match('/%0Afoo') }
158
+ it { should_not match('/foo%2Fbar') }
159
+
160
+ it { should_not match('/foo?') }
161
+ it { should_not match('/foo/bar') }
162
+ it { should_not match('/') }
163
+ it { should_not match('/foo/') }
164
+
165
+ example { pattern.params('/42').should be == {"foo" => 42} }
166
+ it { should expand(foo: 12).to('/12') }
167
+ it { should generate_template('/{foo}') }
168
+ end
169
+
170
+ pattern '/<int:foo>' do
171
+ it { should match('/42').capturing foo: '42' }
172
+
173
+ it { should_not match('/1.0') }
174
+ it { should_not match('/.5') }
175
+ it { should_not match('/foo') }
176
+ it { should_not match('/bar') }
177
+ it { should_not match('/foo.bar') }
178
+ it { should_not match('/%0Afoo') }
179
+ it { should_not match('/foo%2Fbar') }
180
+
181
+ it { should_not match('/foo?') }
182
+ it { should_not match('/foo/bar') }
183
+ it { should_not match('/') }
184
+ it { should_not match('/foo/') }
185
+
186
+ example { pattern.params('/42').should be == {"foo" => 42} }
187
+ it { should expand(foo: 12).to('/12') }
188
+ it { should generate_template('/{foo}') }
189
+ end
190
+
191
+ pattern '/<any(foo,bar):foo>' do
192
+ it { should match('/foo') .capturing foo: 'foo' }
193
+ it { should match('/bar') .capturing foo: 'bar' }
194
+
195
+ it { should_not match('/f') }
196
+ it { should_not match('/fo') }
197
+ it { should_not match('/fooo') }
198
+ it { should_not match('/foo.bar') }
199
+ it { should_not match('/foo?') }
200
+ it { should_not match('/foo/bar') }
201
+ it { should_not match('/') }
202
+ it { should_not match('/foo/') }
203
+ it { should_not match('/baz') }
204
+
205
+ it { should generate_template('/{foo}') }
206
+ end
207
+
208
+ pattern '/<any( foo, bar ):foo>' do
209
+ it { should match('/foo') .capturing foo: 'foo' }
210
+ it { should match('/bar') .capturing foo: 'bar' }
211
+
212
+ it { should_not match('/f') }
213
+ it { should_not match('/fo') }
214
+ it { should_not match('/fooo') }
215
+ it { should_not match('/foo.bar') }
216
+ it { should_not match('/foo?') }
217
+ it { should_not match('/foo/bar') }
218
+ it { should_not match('/') }
219
+ it { should_not match('/foo/') }
220
+ it { should_not match('/baz') }
221
+
222
+ it { should generate_template('/{foo}') }
223
+ end
224
+
225
+ pattern '/<any(foo, bar, "foo,bar"):foo>' do
226
+ it { should match('/foo') .capturing foo: 'foo' }
227
+ it { should match('/bar') .capturing foo: 'bar' }
228
+ it { should match('/foo,bar') .capturing foo: 'foo,bar' }
229
+
230
+ it { should_not match('/f') }
231
+ it { should_not match('/fo') }
232
+ it { should_not match('/fooo') }
233
+ it { should_not match('/foo.bar') }
234
+ it { should_not match('/foo?') }
235
+ it { should_not match('/foo/bar') }
236
+ it { should_not match('/') }
237
+ it { should_not match('/foo/') }
238
+ it { should_not match('/baz') }
239
+
240
+ it { should generate_template('/{foo}') }
241
+ end
242
+
243
+ pattern '/<any(foo, bar, foo\,bar):foo>' do
244
+ it { should match('/foo') .capturing foo: 'foo' }
245
+ it { should match('/bar') .capturing foo: 'bar' }
246
+ it { should match('/foo,bar') .capturing foo: 'foo,bar' }
247
+
248
+ it { should_not match('/f') }
249
+ it { should_not match('/fo') }
250
+ it { should_not match('/fooo') }
251
+ it { should_not match('/foo.bar') }
252
+ it { should_not match('/foo?') }
253
+ it { should_not match('/foo/bar') }
254
+ it { should_not match('/') }
255
+ it { should_not match('/foo/') }
256
+ it { should_not match('/baz') }
257
+
258
+ it { should generate_template('/{foo}') }
259
+ end
260
+
261
+ pattern '/<any(foo, bar, "foo\,bar"):foo>' do
262
+ it { should match('/foo') .capturing foo: 'foo' }
263
+ it { should match('/bar') .capturing foo: 'bar' }
264
+ it { should match('/foo,bar') .capturing foo: 'foo,bar' }
265
+
266
+ it { should_not match('/f') }
267
+ it { should_not match('/fo') }
268
+ it { should_not match('/fooo') }
269
+ it { should_not match('/foo.bar') }
270
+ it { should_not match('/foo?') }
271
+ it { should_not match('/foo/bar') }
272
+ it { should_not match('/') }
273
+ it { should_not match('/foo/') }
274
+ it { should_not match('/baz') }
275
+
276
+ it { should generate_template('/{foo}') }
277
+ end
278
+
279
+ pattern '/<int(min=5,max=50):foo>' do
280
+ example { pattern.params('/42').should be == {"foo" => 42} }
281
+ example { pattern.params('/52').should be == {"foo" => 50} }
282
+ example { pattern.params('/2').should be == {"foo" => 5} }
283
+ end
284
+
285
+ pattern '/<float(min=5,max=50.5):foo>' do
286
+ example { pattern.params('/42.5').should be == {"foo" => 42.5} }
287
+ example { pattern.params('/52.5').should be == {"foo" => 50.5} }
288
+ example { pattern.params('/2.5').should be == {"foo" => 5.0} }
289
+ end
290
+
291
+ pattern '/<prefix>/<float:foo>/<int:bar>' do
292
+ it { should match('/foo/42/42') .capturing foo: '42', bar: '42' }
293
+ it { should match('/foo/1.0/1') .capturing foo: '1.0', bar: '1' }
294
+ it { should match('/foo/.5/0') .capturing foo: '.5', bar: '0' }
295
+
296
+ it { should_not match('/foo/1/1.0') }
297
+ it { should_not match('/foo/1.0/1.0') }
298
+
299
+ it { should generate_template('/{prefix}/{foo}/{bar}') }
300
+
301
+ example do
302
+ pattern.params('/foo/1.0/1').should be == {
303
+ "prefix" => "foo",
304
+ "foo" => 1.0,
305
+ "bar" => 1
306
+ }
307
+ end
308
+ end
309
+
310
+ pattern '/<path:foo>' do
311
+ it { should match('/') .capturing foo: '' }
312
+ it { should match('/foo') .capturing foo: 'foo' }
313
+ it { should match('/foo/bar') .capturing foo: 'foo/bar' }
314
+
315
+ it { should expand .to('/') }
316
+ it { should expand(foo: nil) .to('/') }
317
+ it { should expand(foo: '') .to('/') }
318
+ it { should expand(foo: 'foo') .to('/foo') }
319
+ it { should expand(foo: 'foo/bar') .to('/foo/bar') }
320
+ it { should expand(foo: 'foo.bar') .to('/foo.bar') }
321
+
322
+ it { should generate_template('/{+foo}') }
323
+ end
324
+
325
+ converter = Struct.new(:convert).new(:upcase.to_proc)
326
+ pattern '/<foo:bar>', converters: { foo: converter } do
327
+ it { should match('/foo').capturing bar: 'foo' }
328
+ example { pattern.params('/foo').should be == {"bar" => "FOO"} }
329
+ end
330
+
331
+ context 'invalid syntax' do
332
+ example 'unexpected end of capture' do
333
+ expect { Mustermann::Flask.new('foo>bar') }.
334
+ to raise_error(Mustermann::ParseError, 'unexpected > while parsing "foo>bar"')
335
+ end
336
+
337
+ example 'missing end of capture' do
338
+ expect { Mustermann::Flask.new('foo<bar') }.
339
+ to raise_error(Mustermann::ParseError, 'unexpected end of string while parsing "foo<bar"')
340
+ end
341
+
342
+ example 'unknown converter' do
343
+ expect { Mustermann::Flask.new('foo<bar:name>') }.
344
+ to raise_error(Mustermann::ParseError, 'unexpected converter "bar" while parsing "foo<bar:name>"')
345
+ end
346
+
347
+ example 'broken argument synax' do
348
+ expect { Mustermann::Flask.new('<string(length=3=2):foo>') }.
349
+ to raise_error(Mustermann::ParseError, 'unexpected = while parsing "<string(length=3=2):foo>"')
350
+ end
351
+
352
+ example 'missing )' do
353
+ expect { Mustermann::Flask.new('<string(foo') }.
354
+ to raise_error(Mustermann::ParseError, 'unexpected end of string while parsing "<string(foo"')
355
+ end
356
+
357
+ example 'missing ""' do
358
+ expect { Mustermann::Flask.new('<string("foo') }.
359
+ to raise_error(Mustermann::ParseError, 'unexpected end of string while parsing "<string(\\"foo"')
360
+ end
361
+ end
362
+ end
@@ -0,0 +1,369 @@
1
+ # frozen_string_literal: true
2
+ require 'support'
3
+ require 'mustermann/flask'
4
+
5
+ FlaskSubclass ||= Class.new(Mustermann::Flask)
6
+ FlaskSubclass.register_converter(:foo) {}
7
+
8
+ describe FlaskSubclass do
9
+ extend Support::Pattern
10
+
11
+ pattern '' do
12
+ it { should match('') }
13
+ it { should_not match('/') }
14
+
15
+ it { should expand.to('') }
16
+ it { should_not expand(a: 1) }
17
+
18
+ it { should generate_template('') }
19
+
20
+ it { should respond_to(:expand) }
21
+ it { should respond_to(:to_templates) }
22
+ end
23
+
24
+ pattern '/' do
25
+ it { should match('/') }
26
+ it { should_not match('/foo') }
27
+
28
+ it { should expand.to('/') }
29
+ it { should_not expand(a: 1) }
30
+ end
31
+
32
+ pattern '/foo' do
33
+ it { should match('/foo') }
34
+ it { should_not match('/bar') }
35
+ it { should_not match('/foo.bar') }
36
+
37
+ it { should expand.to('/foo') }
38
+ it { should_not expand(a: 1) }
39
+ end
40
+
41
+ pattern '/foo/bar' do
42
+ it { should match('/foo/bar') }
43
+ it { should_not match('/foo%2Fbar') }
44
+ it { should_not match('/foo%2fbar') }
45
+
46
+ it { should expand.to('/foo/bar') }
47
+ it { should_not expand(a: 1) }
48
+ end
49
+
50
+ pattern '/<foo>' do
51
+ it { should match('/foo') .capturing foo: 'foo' }
52
+ it { should match('/bar') .capturing foo: 'bar' }
53
+ it { should match('/foo.bar') .capturing foo: 'foo.bar' }
54
+ it { should match('/%0Afoo') .capturing foo: '%0Afoo' }
55
+ it { should match('/foo%2Fbar') .capturing foo: 'foo%2Fbar' }
56
+
57
+ it { should_not match('/foo?') }
58
+ it { should_not match('/foo/bar') }
59
+ it { should_not match('/') }
60
+ it { should_not match('/foo/') }
61
+
62
+ example { pattern.params('/foo') .should be == {"foo" => "foo"} }
63
+ example { pattern.params('/f%20o') .should be == {"foo" => "f o"} }
64
+ example { pattern.params('').should be_nil }
65
+
66
+ it { should expand(foo: 'bar') .to('/bar') }
67
+ it { should expand(foo: 'b r') .to('/b%20r') }
68
+ it { should expand(foo: 'foo/bar') .to('/foo%2Fbar') }
69
+
70
+ it { should_not expand(foo: 'foo', bar: 'bar') }
71
+ it { should_not expand(bar: 'bar') }
72
+ it { should_not expand }
73
+
74
+ it { should generate_template('/{foo}') }
75
+ end
76
+
77
+ pattern '/<string:foo>' do
78
+ it { should match('/foo') .capturing foo: 'foo' }
79
+ it { should match('/bar') .capturing foo: 'bar' }
80
+ it { should match('/foo.bar') .capturing foo: 'foo.bar' }
81
+ it { should match('/%0Afoo') .capturing foo: '%0Afoo' }
82
+ it { should match('/foo%2Fbar') .capturing foo: 'foo%2Fbar' }
83
+
84
+ it { should_not match('/foo?') }
85
+ it { should_not match('/foo/bar') }
86
+ it { should_not match('/') }
87
+ it { should_not match('/foo/') }
88
+
89
+ example { pattern.params('/foo') .should be == {"foo" => "foo"} }
90
+ example { pattern.params('/f%20o') .should be == {"foo" => "f o"} }
91
+ example { pattern.params('').should be_nil }
92
+
93
+ it { should expand(foo: 'bar') .to('/bar') }
94
+ it { should expand(foo: 'b r') .to('/b%20r') }
95
+ it { should expand(foo: 'foo/bar') .to('/foo%2Fbar') }
96
+
97
+ it { should_not expand(foo: 'foo', bar: 'bar') }
98
+ it { should_not expand(bar: 'bar') }
99
+ it { should_not expand }
100
+
101
+ it { should generate_template('/{foo}') }
102
+ end
103
+
104
+ pattern '/<string(minlength=2):foo>' do
105
+ it { should match('/foo') .capturing foo: 'foo' }
106
+ it { should match('/bar') .capturing foo: 'bar' }
107
+ it { should match('/foo.bar') .capturing foo: 'foo.bar' }
108
+ it { should match('/%0Afoo') .capturing foo: '%0Afoo' }
109
+ it { should match('/foo%2Fbar') .capturing foo: 'foo%2Fbar' }
110
+
111
+ it { should_not match('/f') }
112
+ it { should_not match('/foo?') }
113
+ it { should_not match('/foo/bar') }
114
+ it { should_not match('/') }
115
+ it { should_not match('/foo/') }
116
+
117
+ it { should generate_template('/{foo}') }
118
+ end
119
+
120
+ pattern '/<string(maxlength=3):foo>' do
121
+ it { should match('/f') .capturing foo: 'f' }
122
+ it { should match('/fo') .capturing foo: 'fo' }
123
+ it { should match('/foo') .capturing foo: 'foo' }
124
+ it { should match('/bar') .capturing foo: 'bar' }
125
+
126
+ it { should_not match('/fooo') }
127
+ it { should_not match('/foo.bar') }
128
+ it { should_not match('/foo?') }
129
+ it { should_not match('/foo/bar') }
130
+ it { should_not match('/') }
131
+ it { should_not match('/foo/') }
132
+
133
+ it { should generate_template('/{foo}') }
134
+ end
135
+
136
+ pattern '/<string(length=3):foo>' do
137
+ it { should match('/foo') .capturing foo: 'foo' }
138
+ it { should match('/bar') .capturing foo: 'bar' }
139
+
140
+ it { should_not match('/f') }
141
+ it { should_not match('/fo') }
142
+ it { should_not match('/fooo') }
143
+ it { should_not match('/foo.bar') }
144
+ it { should_not match('/foo?') }
145
+ it { should_not match('/foo/bar') }
146
+ it { should_not match('/') }
147
+ it { should_not match('/foo/') }
148
+
149
+ it { should generate_template('/{foo}') }
150
+ end
151
+
152
+ pattern '/<int:foo>' do
153
+ it { should match('/42').capturing foo: '42' }
154
+
155
+ it { should_not match('/1.0') }
156
+ it { should_not match('/.5') }
157
+ it { should_not match('/foo') }
158
+ it { should_not match('/bar') }
159
+ it { should_not match('/foo.bar') }
160
+ it { should_not match('/%0Afoo') }
161
+ it { should_not match('/foo%2Fbar') }
162
+
163
+ it { should_not match('/foo?') }
164
+ it { should_not match('/foo/bar') }
165
+ it { should_not match('/') }
166
+ it { should_not match('/foo/') }
167
+
168
+ example { pattern.params('/42').should be == {"foo" => 42} }
169
+ it { should expand(foo: 12).to('/12') }
170
+ it { should generate_template('/{foo}') }
171
+ end
172
+
173
+ pattern '/<int:foo>' do
174
+ it { should match('/42').capturing foo: '42' }
175
+
176
+ it { should_not match('/1.0') }
177
+ it { should_not match('/.5') }
178
+ it { should_not match('/foo') }
179
+ it { should_not match('/bar') }
180
+ it { should_not match('/foo.bar') }
181
+ it { should_not match('/%0Afoo') }
182
+ it { should_not match('/foo%2Fbar') }
183
+
184
+ it { should_not match('/foo?') }
185
+ it { should_not match('/foo/bar') }
186
+ it { should_not match('/') }
187
+ it { should_not match('/foo/') }
188
+
189
+ example { pattern.params('/42').should be == {"foo" => 42} }
190
+ it { should expand(foo: 12).to('/12') }
191
+ it { should generate_template('/{foo}') }
192
+ end
193
+
194
+ pattern '/<any(foo,bar):foo>' do
195
+ it { should match('/foo') .capturing foo: 'foo' }
196
+ it { should match('/bar') .capturing foo: 'bar' }
197
+
198
+ it { should_not match('/f') }
199
+ it { should_not match('/fo') }
200
+ it { should_not match('/fooo') }
201
+ it { should_not match('/foo.bar') }
202
+ it { should_not match('/foo?') }
203
+ it { should_not match('/foo/bar') }
204
+ it { should_not match('/') }
205
+ it { should_not match('/foo/') }
206
+ it { should_not match('/baz') }
207
+
208
+ it { should generate_template('/{foo}') }
209
+ end
210
+
211
+ pattern '/<any( foo, bar ):foo>' do
212
+ it { should match('/foo') .capturing foo: 'foo' }
213
+ it { should match('/bar') .capturing foo: 'bar' }
214
+
215
+ it { should_not match('/f') }
216
+ it { should_not match('/fo') }
217
+ it { should_not match('/fooo') }
218
+ it { should_not match('/foo.bar') }
219
+ it { should_not match('/foo?') }
220
+ it { should_not match('/foo/bar') }
221
+ it { should_not match('/') }
222
+ it { should_not match('/foo/') }
223
+ it { should_not match('/baz') }
224
+
225
+ it { should generate_template('/{foo}') }
226
+ end
227
+
228
+ pattern '/<any(foo, bar, "foo,bar"):foo>' do
229
+ it { should match('/foo') .capturing foo: 'foo' }
230
+ it { should match('/bar') .capturing foo: 'bar' }
231
+ it { should match('/foo,bar') .capturing foo: 'foo,bar' }
232
+
233
+ it { should_not match('/f') }
234
+ it { should_not match('/fo') }
235
+ it { should_not match('/fooo') }
236
+ it { should_not match('/foo.bar') }
237
+ it { should_not match('/foo?') }
238
+ it { should_not match('/foo/bar') }
239
+ it { should_not match('/') }
240
+ it { should_not match('/foo/') }
241
+ it { should_not match('/baz') }
242
+
243
+ it { should generate_template('/{foo}') }
244
+ end
245
+
246
+ pattern '/<any(foo, bar, foo\,bar):foo>' do
247
+ it { should match('/foo') .capturing foo: 'foo' }
248
+ it { should match('/bar') .capturing foo: 'bar' }
249
+ it { should match('/foo,bar') .capturing foo: 'foo,bar' }
250
+
251
+ it { should_not match('/f') }
252
+ it { should_not match('/fo') }
253
+ it { should_not match('/fooo') }
254
+ it { should_not match('/foo.bar') }
255
+ it { should_not match('/foo?') }
256
+ it { should_not match('/foo/bar') }
257
+ it { should_not match('/') }
258
+ it { should_not match('/foo/') }
259
+ it { should_not match('/baz') }
260
+
261
+ it { should generate_template('/{foo}') }
262
+ end
263
+
264
+ pattern '/<any(foo, bar, "foo\,bar"):foo>' do
265
+ it { should match('/foo') .capturing foo: 'foo' }
266
+ it { should match('/bar') .capturing foo: 'bar' }
267
+ it { should match('/foo,bar') .capturing foo: 'foo,bar' }
268
+
269
+ it { should_not match('/f') }
270
+ it { should_not match('/fo') }
271
+ it { should_not match('/fooo') }
272
+ it { should_not match('/foo.bar') }
273
+ it { should_not match('/foo?') }
274
+ it { should_not match('/foo/bar') }
275
+ it { should_not match('/') }
276
+ it { should_not match('/foo/') }
277
+ it { should_not match('/baz') }
278
+
279
+ it { should generate_template('/{foo}') }
280
+ end
281
+
282
+ pattern '/<int(min=5,max=50):foo>' do
283
+ example { pattern.params('/42').should be == {"foo" => 42} }
284
+ example { pattern.params('/52').should be == {"foo" => 50} }
285
+ example { pattern.params('/2').should be == {"foo" => 5} }
286
+ end
287
+
288
+ pattern '/<float(min=5,max=50.5):foo>' do
289
+ example { pattern.params('/42.5').should be == {"foo" => 42.5} }
290
+ example { pattern.params('/52.5').should be == {"foo" => 50.5} }
291
+ example { pattern.params('/2.5').should be == {"foo" => 5.0} }
292
+ end
293
+
294
+ pattern '/<prefix>/<float:foo>/<int:bar>' do
295
+ it { should match('/foo/42/42') .capturing foo: '42', bar: '42' }
296
+ it { should match('/foo/1.0/1') .capturing foo: '1.0', bar: '1' }
297
+ it { should match('/foo/.5/0') .capturing foo: '.5', bar: '0' }
298
+
299
+ it { should_not match('/foo/1/1.0') }
300
+ it { should_not match('/foo/1.0/1.0') }
301
+
302
+ it { should generate_template('/{prefix}/{foo}/{bar}') }
303
+
304
+ example do
305
+ pattern.params('/foo/1.0/1').should be == {
306
+ "prefix" => "foo",
307
+ "foo" => 1.0,
308
+ "bar" => 1
309
+ }
310
+ end
311
+ end
312
+
313
+ pattern '/<path:foo>' do
314
+ it { should match('/') .capturing foo: '' }
315
+ it { should match('/foo') .capturing foo: 'foo' }
316
+ it { should match('/foo/bar') .capturing foo: 'foo/bar' }
317
+
318
+ it { should expand .to('/') }
319
+ it { should expand(foo: nil) .to('/') }
320
+ it { should expand(foo: '') .to('/') }
321
+ it { should expand(foo: 'foo') .to('/foo') }
322
+ it { should expand(foo: 'foo/bar') .to('/foo/bar') }
323
+ it { should expand(foo: 'foo.bar') .to('/foo.bar') }
324
+
325
+ it { should generate_template('/{+foo}') }
326
+ end
327
+
328
+ pattern '/<foo:bar>' do
329
+ it { should match('/foo').capturing bar: 'foo' }
330
+ it { should generate_template('/{bar}') }
331
+
332
+ example do
333
+ expect { Mustermann::Flask.new('/<foo:bar>') }.to \
334
+ raise_error(Mustermann::ParseError, 'unexpected converter "foo" while parsing "/<foo:bar>"')
335
+ end
336
+ end
337
+
338
+ context 'invalid syntax' do
339
+ example 'unexpected end of capture' do
340
+ expect { FlaskSubclass.new('foo>bar') }.
341
+ to raise_error(Mustermann::ParseError, 'unexpected > while parsing "foo>bar"')
342
+ end
343
+
344
+ example 'missing end of capture' do
345
+ expect { FlaskSubclass.new('foo<bar') }.
346
+ to raise_error(Mustermann::ParseError, 'unexpected end of string while parsing "foo<bar"')
347
+ end
348
+
349
+ example 'unknown converter' do
350
+ expect { FlaskSubclass.new('foo<bar:name>') }.
351
+ to raise_error(Mustermann::ParseError, 'unexpected converter "bar" while parsing "foo<bar:name>"')
352
+ end
353
+
354
+ example 'broken argument synax' do
355
+ expect { FlaskSubclass.new('<string(length=3=2):foo>') }.
356
+ to raise_error(Mustermann::ParseError, 'unexpected = while parsing "<string(length=3=2):foo>"')
357
+ end
358
+
359
+ example 'missing )' do
360
+ expect { FlaskSubclass.new('<string(foo') }.
361
+ to raise_error(Mustermann::ParseError, 'unexpected end of string while parsing "<string(foo"')
362
+ end
363
+
364
+ example 'missing ""' do
365
+ expect { FlaskSubclass.new('<string("foo') }.
366
+ to raise_error(Mustermann::ParseError, 'unexpected end of string while parsing "<string(\\"foo"')
367
+ end
368
+ end
369
+ end