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,296 @@
1
+ require 'support'
2
+ require 'mustermann/extension'
3
+ require 'sinatra/base'
4
+ require 'rack/test'
5
+
6
+ describe Mustermann::Extension do
7
+ include Rack::Test::Methods
8
+
9
+ subject :app do
10
+ Sinatra.new do
11
+ set :environment, :test
12
+ register Mustermann
13
+ end
14
+ end
15
+
16
+ it 'sets up the extension' do
17
+ app.should be_a(Mustermann::Extension)
18
+ end
19
+
20
+ context 'uses Sinatra-style patterns by default' do
21
+ before { app.get('/:slug(.:extension)?') { params[:slug] } }
22
+ example { get('/foo') .body.should be == 'foo' }
23
+ example { get('/foo.') .body.should be == 'foo.' }
24
+ example { get('/foo.bar') .body.should be == 'foo' }
25
+ example { get('/a%20b') .body.should be == 'a b' }
26
+ end
27
+
28
+ describe :except do
29
+ before { app.get('/auth/*', except: '/auth/login') { 'ok' } }
30
+ example { get('/auth/dunno').should be_ok }
31
+ example { get('/auth/login').should_not be_ok }
32
+ end
33
+
34
+ describe :capture do
35
+ context 'global' do
36
+ before do
37
+ app.set(:pattern, capture: { ext: %w[png jpg gif] })
38
+ app.get('/:slug(.:ext)?') { params[:slug] }
39
+ end
40
+
41
+ example { get('/foo.bar').body.should be == 'foo.bar' }
42
+ example { get('/foo.png').body.should be == 'foo' }
43
+ end
44
+
45
+ context 'route local' do
46
+ before do
47
+ app.set(:pattern, nil)
48
+ app.get('/:id', capture: /\d+/) { 'ok' }
49
+ end
50
+
51
+ example { get('/42').should be_ok }
52
+ example { get('/foo').should_not be_ok }
53
+ end
54
+
55
+ context 'global and route local' do
56
+ context 'global is a hash' do
57
+ before do
58
+ app.set(:pattern, capture: { id: /\d+/ })
59
+ app.get('/:id(.:ext)?', capture: { ext: 'png' }) { ?a }
60
+ app.get('/:id', capture: { id: 'foo' }) { ?b }
61
+ app.get('/:id', capture: :alpha) { ?c }
62
+ end
63
+
64
+ example { get('/20') .body.should be == ?a }
65
+ example { get('/20.png') .body.should be == ?a }
66
+ example { get('/foo') .body.should be == ?b }
67
+ example { get('/bar') .body.should be == ?c }
68
+ end
69
+
70
+ context 'global is not a hash' do
71
+ before do
72
+ app.set(:pattern, capture: /\d+/)
73
+ app.get('/:slug(.:ext)?', capture: { ext: 'png' }) { params[:slug] }
74
+ app.get('/:slug', capture: :alpha) { 'ok' }
75
+ end
76
+
77
+ example { get('/20.png').should be_ok }
78
+ example { get('/foo.png').should_not be_ok }
79
+ example { get('/foo').should be_ok }
80
+
81
+ example { get('/20.png') .body.should be == '20' }
82
+ example { get('/42') .body.should be == '42' }
83
+ example { get('/foo') .body.should be == 'ok' }
84
+ end
85
+ end
86
+ end
87
+
88
+ describe :pattern do
89
+ describe :except do
90
+ before { app.get('/auth/*', pattern: { except: '/auth/login' }) { 'ok' } }
91
+ example { get('/auth/dunno').should be_ok }
92
+ example { get('/auth/login').should_not be_ok }
93
+ end
94
+
95
+ describe :capture do
96
+ context 'route local' do
97
+ before do
98
+ app.set(:pattern, nil)
99
+ app.get('/:id', pattern: { capture: /\d+/ }) { 'ok' }
100
+ end
101
+
102
+ example { get('/42').should be_ok }
103
+ example { get('/foo').should_not be_ok }
104
+ end
105
+
106
+ context 'global and route local' do
107
+ context 'global is a hash' do
108
+ before do
109
+ app.set(:pattern, capture: { id: /\d+/ })
110
+ app.get('/:id(.:ext)?', pattern: { capture: { ext: 'png' }}) { ?a }
111
+ app.get('/:id', pattern: { capture: { id: 'foo' }}) { ?b }
112
+ app.get('/:id', pattern: { capture: :alpha }) { ?c }
113
+ end
114
+
115
+ example { get('/20') .body.should be == ?a }
116
+ example { get('/20.png') .body.should be == ?a }
117
+ example { get('/foo') .body.should be == ?b }
118
+ example { get('/bar') .body.should be == ?c }
119
+ end
120
+
121
+ context 'global is not a hash' do
122
+ before do
123
+ app.set(:pattern, capture: /\d+/)
124
+ app.get('/:slug(.:ext)?', pattern: { capture: { ext: 'png' }}) { params[:slug] }
125
+ app.get('/:slug', pattern: { capture: :alpha }) { 'ok' }
126
+ end
127
+
128
+ example { get('/20.png').should be_ok }
129
+ example { get('/foo.png').should_not be_ok }
130
+ example { get('/foo').should be_ok }
131
+
132
+ example { get('/20.png') .body.should be == '20' }
133
+ example { get('/42') .body.should be == '42' }
134
+ example { get('/foo') .body.should be == 'ok' }
135
+ end
136
+ end
137
+ end
138
+
139
+ describe :greedy do
140
+ context 'default' do
141
+ before { app.get('/:name.:ext') { params[:name] }}
142
+ example { get('/foo.bar') .body.should be == 'foo' }
143
+ example { get('/foo.bar.baz') .body.should be == 'foo.bar' }
144
+ end
145
+
146
+ context 'enabled' do
147
+ before { app.get('/:name.:ext', pattern: { greedy: true }) { params[:name] }}
148
+ example { get('/foo.bar') .body.should be == 'foo' }
149
+ example { get('/foo.bar.baz') .body.should be == 'foo.bar' }
150
+ end
151
+
152
+ context 'disabled' do
153
+ before { app.get('/:name.:ext', pattern: { greedy: false }) { params[:name] }}
154
+ example { get('/foo.bar') .body.should be == 'foo' }
155
+ example { get('/foo.bar.baz') .body.should be == 'foo' }
156
+ end
157
+
158
+ context 'global' do
159
+ before do
160
+ app.set(:pattern, greedy: false)
161
+ app.get('/:name.:ext') { params[:name] }
162
+ end
163
+
164
+ example { get('/foo.bar') .body.should be == 'foo' }
165
+ example { get('/foo.bar.baz') .body.should be == 'foo' }
166
+ end
167
+ end
168
+
169
+ describe :space_matches_plus do
170
+ context 'default' do
171
+ before { app.get('/foo bar') { 'ok' }}
172
+ example { get('/foo%20bar') .should be_ok }
173
+ example { get('/foo+bar') .should be_ok }
174
+ end
175
+
176
+ context 'enabled' do
177
+ before { app.get('/foo bar', pattern: { space_matches_plus: true }) { 'ok' }}
178
+ example { get('/foo%20bar') .should be_ok }
179
+ example { get('/foo+bar') .should be_ok }
180
+ end
181
+
182
+ context 'disabled' do
183
+ before { app.get('/foo bar', pattern: { space_matches_plus: false }) { 'ok' }}
184
+ example { get('/foo%20bar') .should be_ok }
185
+ example { get('/foo+bar') .should_not be_ok }
186
+ end
187
+
188
+ context 'global' do
189
+ before do
190
+ app.set(:pattern, space_matches_plus: false)
191
+ app.get('/foo bar') { 'ok' }
192
+ end
193
+
194
+ example { get('/foo%20bar') .should be_ok }
195
+ example { get('/foo+bar') .should_not be_ok }
196
+ end
197
+ end
198
+
199
+ describe :uri_decode do
200
+ context 'default' do
201
+ before { app.get('/&') { 'ok' }}
202
+ example { get('/&') .should be_ok }
203
+ example { get('/%26') .should be_ok }
204
+ end
205
+
206
+ context 'enabled' do
207
+ before { app.get('/&', pattern: { uri_decode: true }) { 'ok' }}
208
+ example { get('/&') .should be_ok }
209
+ example { get('/%26') .should be_ok }
210
+ end
211
+
212
+ context 'disabled' do
213
+ before { app.get('/&', pattern: { uri_decode: false }) { 'ok' }}
214
+ example { get('/&') .should be_ok }
215
+ example { get('/%26') .should_not be_ok }
216
+ end
217
+
218
+ context 'global' do
219
+ before do
220
+ app.set(:pattern, uri_decode: false)
221
+ app.get('/&') { 'ok' }
222
+ end
223
+
224
+ example { get('/&') .should be_ok }
225
+ example { get('/%26') .should_not be_ok }
226
+ end
227
+ end
228
+ end
229
+
230
+ describe :type do
231
+ describe :identity do
232
+ before do
233
+ app.set(:pattern, type: :identity)
234
+ app.get('/:foo') { 'ok' }
235
+ end
236
+
237
+ example { get('/:foo').should be_ok }
238
+ example { get('/foo').should_not be_ok }
239
+ end
240
+
241
+ describe :rails do
242
+ before do
243
+ app.set(:pattern, type: :rails)
244
+ app.get('/:slug(.:extension)') { params[:slug] }
245
+ end
246
+
247
+ example { get('/foo') .body.should be == 'foo' }
248
+ example { get('/foo.') .body.should be == 'foo.' }
249
+ example { get('/foo.bar') .body.should be == 'foo' }
250
+ example { get('/a%20b') .body.should be == 'a b' }
251
+ end
252
+
253
+ describe :shell do
254
+ before do
255
+ app.set(:pattern, type: :shell)
256
+ app.get('/{foo,bar}') { 'ok' }
257
+ end
258
+
259
+ example(nil, skip: true) { get('/foo').should be_ok }
260
+ example(nil, skip: true) { get('/bar').should be_ok }
261
+ end
262
+
263
+ describe :simple do
264
+ before do
265
+ app.set(:pattern, type: :simple)
266
+ app.get('/(a)') { 'ok' }
267
+ end
268
+
269
+ example { get('/(a)').should be_ok }
270
+ example { get('/a').should_not be_ok }
271
+ end
272
+
273
+ describe :simple do
274
+ before do
275
+ app.set(:pattern, type: :template)
276
+ app.get('/foo{/segments*}{.ext}') { "%p %p" % [params[:segments], params[:ext]] }
277
+ end
278
+
279
+ example { get('/foo/a.png').should be_ok }
280
+ example { get('/foo/a').should_not be_ok }
281
+
282
+ example { get('/foo/a.png').body.should be == '["a"] "png"' }
283
+ example { get('/foo/a/b.png').body.should be == '["a", "b"] "png"' }
284
+ end
285
+ end
286
+
287
+ context 'works with filters' do
288
+ before do
289
+ app.before('/auth/*', except: '/auth/login') { halt 'auth required' }
290
+ app.get('/auth/login') { 'please log in' }
291
+ end
292
+
293
+ example { get('/auth/dunno').body.should be == 'auth required' }
294
+ example { get('/auth/login').body.should be == 'please log in' }
295
+ end
296
+ end
@@ -0,0 +1,83 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require 'support'
3
+ require 'mustermann/identity'
4
+
5
+ describe Mustermann::Identity 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
+
17
+ example { pattern.params('/').should be == {} }
18
+ example { pattern.params('').should be_nil }
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 match('/foo%2Fbar') }
30
+ it { should 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_not match('/path%2Bwith%2Bspaces') }
60
+ it { should_not 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 '/foo/bar', uri_decode: false do
73
+ it { should match('/foo/bar') }
74
+ it { should_not match('/foo%2Fbar') }
75
+ it { should_not match('/foo%2fbar') }
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
+ end
@@ -0,0 +1,83 @@
1
+ require 'support'
2
+ require 'mustermann/mapper'
3
+
4
+ describe Mustermann::Mapper do
5
+ describe :initialize do
6
+ context 'accepts a block with no arguments, using the return value' do
7
+ subject(:mapper) { Mustermann::Mapper.new(additional_values: :raise) {{ "/foo" => "/bar" }}}
8
+ its(:to_h) { should be == { Mustermann.new("/foo") => Mustermann::Expander.new("/bar") } }
9
+ example { mapper['/foo'].should be == '/bar' }
10
+ example { mapper['/fox'].should be == '/fox' }
11
+ end
12
+
13
+ context 'accepts a block with argument, passes instance to it' do
14
+ subject(:mapper) { Mustermann::Mapper.new(additional_values: :raise) { |m| m["/foo"] = "/bar" }}
15
+ its(:to_h) { should be == { Mustermann.new("/foo") => Mustermann::Expander.new("/bar") } }
16
+ example { mapper['/foo'].should be == '/bar' }
17
+ example { mapper['/fox'].should be == '/fox' }
18
+ end
19
+
20
+ context 'accepts mappings followed by options' do
21
+ subject(:mapper) { Mustermann::Mapper.new("/foo" => "/bar", additional_values: :raise) }
22
+ its(:to_h) { should be == { Mustermann.new("/foo") => Mustermann::Expander.new("/bar") } }
23
+ example { mapper['/foo'].should be == '/bar' }
24
+ example { mapper['/fox'].should be == '/fox' }
25
+ end
26
+
27
+ context 'accepts options followed by mappings' do
28
+ subject(:mapper) { Mustermann::Mapper.new(additional_values: :raise, "/foo" => "/bar") }
29
+ its(:to_h) { should be == { Mustermann.new("/foo") => Mustermann::Expander.new("/bar") } }
30
+ example { mapper['/foo'].should be == '/bar' }
31
+ example { mapper['/fox'].should be == '/fox' }
32
+ end
33
+
34
+ context 'allows specifying type' do
35
+ subject(:mapper) { Mustermann::Mapper.new(additional_values: :raise, type: :rails, "/foo" => "/bar") }
36
+ its(:to_h) { should be == { Mustermann.new("/foo", type: :rails) => Mustermann::Expander.new("/bar", type: :rails) } }
37
+ example { mapper['/foo'].should be == '/bar' }
38
+ example { mapper['/fox'].should be == '/fox' }
39
+ end
40
+ end
41
+
42
+ describe :convert do
43
+ subject(:mapper) { Mustermann::Mapper.new }
44
+
45
+ context 'it maps params' do
46
+ before { mapper["/:a"] = "/:a.html" }
47
+ example { mapper["/foo"] .should be == "/foo.html" }
48
+ example { mapper["/foo/bar"] .should be == "/foo/bar" }
49
+ end
50
+
51
+ context 'it supports named splats' do
52
+ before { mapper["/*a"] = "/*a.html" }
53
+ example { mapper["/foo"] .should be == "/foo.html" }
54
+ example { mapper["/foo/bar"] .should be == "/foo/bar.html" }
55
+ end
56
+
57
+ context 'can map from patterns' do
58
+ before { mapper[Mustermann.new("/:a")] = "/:a.html" }
59
+ example { mapper["/foo"] .should be == "/foo.html" }
60
+ example { mapper["/foo/bar"] .should be == "/foo/bar" }
61
+ end
62
+
63
+ context 'can map to patterns' do
64
+ before { mapper[Mustermann.new("/:a")] = Mustermann.new("/:a.html") }
65
+ example { mapper["/foo"] .should be == "/foo.html" }
66
+ example { mapper["/foo/bar"] .should be == "/foo/bar" }
67
+ end
68
+
69
+ context 'can map to expanders' do
70
+ before { mapper[Mustermann.new("/:a")] = Mustermann::Expander.new("/:a.html") }
71
+ example { mapper["/foo"] .should be == "/foo.html" }
72
+ example { mapper["/foo/bar"] .should be == "/foo/bar" }
73
+ end
74
+
75
+ context 'can map to array' do
76
+ before { mapper["/:a"] = ["/:a.html", "/:a.:f"] }
77
+ example { mapper["/foo"] .should be == "/foo.html" }
78
+ example { mapper["/foo", "f" => 'x'] .should be == "/foo.x" }
79
+ example { mapper["/foo", f: 'x'] .should be == "/foo.x" }
80
+ example { mapper["/foo/bar"] .should be == "/foo/bar" }
81
+ end
82
+ end
83
+ end