mustermann19 0.3.1

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 (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