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,8 @@
1
+ require 'support'
2
+ require 'mustermann/regexp_based'
3
+
4
+ describe Mustermann::RegexpBased do
5
+ it 'raises a NotImplementedError when used directly' do
6
+ expect { Mustermann::RegexpBased.new("") === "" }.to raise_error(NotImplementedError)
7
+ end
8
+ end
@@ -0,0 +1,36 @@
1
+ require 'support'
2
+ require 'mustermann/regular'
3
+
4
+ describe Mustermann::Regular do
5
+ extend Support::Pattern
6
+
7
+ pattern '' do
8
+ it { should match('') }
9
+ it { should_not match('/') }
10
+ end
11
+
12
+ pattern '/' do
13
+ it { should match('/') }
14
+ it { should_not match('/foo') }
15
+ end
16
+
17
+ pattern '/foo' do
18
+ it { should match('/foo') }
19
+ it { should_not match('/bar') }
20
+ it { should_not match('/foo.bar') }
21
+ end
22
+
23
+ pattern '/foo/bar' do
24
+ it { should match('/foo/bar') }
25
+ it { should_not match('/foo%2Fbar') }
26
+ it { should_not match('/foo%2fbar') }
27
+ end
28
+
29
+ pattern '/(?<foo>.*)' do
30
+ it { should match('/foo') .capturing foo: 'foo' }
31
+ it { should match('/bar') .capturing foo: 'bar' }
32
+ it { should match('/foo.bar') .capturing foo: 'foo.bar' }
33
+ it { should match('/%0Afoo') .capturing foo: '%0Afoo' }
34
+ it { should match('/foo%2Fbar') .capturing foo: 'foo%2Fbar' }
35
+ end
36
+ end
@@ -0,0 +1,39 @@
1
+ require 'mustermann/router/rack'
2
+
3
+ describe Mustermann::Router::Rack do
4
+ include Rack::Test::Methods
5
+ subject(:app) { Mustermann::Router::Rack.new }
6
+
7
+ context 'matching' do
8
+ before { app.on('/foo') { [418, {'Content-Type' => 'text/plain'}, 'bar'] } }
9
+ example { get('/foo').status.should be == 418 }
10
+ example { get('/bar').status.should be == 404 }
11
+ end
12
+
13
+ context "params" do
14
+ before { app.on('/:name') { |e| [200, {'Content-Type' => 'text/plain'}, e['mustermann.params']['name']] } }
15
+ example { get('/foo').body.should be == 'foo' }
16
+ example { get('/bar').body.should be == 'bar' }
17
+ end
18
+
19
+ context 'X-Cascade: pass' do
20
+ before do
21
+ app.on('/') { [200, { 'X-Cascade' => 'pass' }, ['a']] }
22
+ app.on('/') { [200, { 'x-cascade' => 'pass' }, ['b']] }
23
+ app.on('/') { [200, { 'Content-Type' => 'text/plain' }, ['c']] }
24
+ app.on('/') { [200, { 'Content-Type' => 'text/plain' }, ['d']] }
25
+ end
26
+
27
+ example { get('/').body.should be == 'c' }
28
+ end
29
+
30
+ context 'throw :pass' do
31
+ before do
32
+ app.on('/') { throw :pass }
33
+ app.on('/') { [200, { 'Content-Type' => 'text/plain' }, ['b']] }
34
+ app.on('/') { [200, { 'Content-Type' => 'text/plain' }, ['c']] }
35
+ end
36
+
37
+ example { get('/').body.should be == 'b' }
38
+ end
39
+ end
@@ -0,0 +1,32 @@
1
+ require 'mustermann/router/simple'
2
+
3
+ describe Mustermann::Router::Simple do
4
+ describe :initialize do
5
+ context "with implicit receiver" do
6
+ subject(:router) { Mustermann::Router::Simple.new { on('/foo') { 'bar' } } }
7
+ example { router.call('/foo').should be == 'bar' }
8
+ end
9
+
10
+ context "with explicit receiver" do
11
+ subject(:router) { Mustermann::Router::Simple.new { |r| r.on('/foo') { 'bar' } } }
12
+ example { router.call('/foo').should be == 'bar' }
13
+ end
14
+
15
+ context "with default" do
16
+ subject(:router) { Mustermann::Router::Simple.new(default: 'bar') }
17
+ example { router.call('/foo').should be == 'bar' }
18
+ end
19
+ end
20
+
21
+ describe :[]= do
22
+ subject(:router) { Mustermann::Router::Simple.new }
23
+ before { router['/:name'] = proc { |*a| a } }
24
+ example { router.call('/foo').should be == ['/foo', "name" => 'foo'] }
25
+ end
26
+
27
+ describe :[] do
28
+ subject(:router) { Mustermann::Router::Simple.new }
29
+ before { router.on('/x') { 42 } }
30
+ example { router['/x'].call.should be == 42 }
31
+ end
32
+ end
@@ -0,0 +1,109 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require 'support'
3
+ require 'mustermann/shell'
4
+
5
+ describe Mustermann::Shell 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 '/*/bar' do
34
+ it { should match('/foo/bar') }
35
+ it { should match('/bar/bar') }
36
+ it { should match('/foo%2Fbar') }
37
+ it { should match('/foo%2fbar') }
38
+ it { should_not match('/foo/foo/bar') }
39
+ it { should_not match('/bar/foo') }
40
+ end
41
+
42
+ pattern '/**/foo' do
43
+ it { should match('/a/b/c/foo') }
44
+ it { should match('/a/b/c/foo') }
45
+ it { should match('/a/.b/c/foo') }
46
+ it { should match('/a/.b/c/foo') }
47
+ end
48
+
49
+ pattern '/:foo' do
50
+ it { should match('/:foo') }
51
+ it { should match('/%3Afoo') }
52
+ it { should_not match('/foo') }
53
+ it { should_not match('/foo?') }
54
+ it { should_not match('/foo/bar') }
55
+ it { should_not match('/') }
56
+ it { should_not match('/foo/') }
57
+ end
58
+
59
+ pattern '/föö' do
60
+ it { should match("/f%C3%B6%C3%B6") }
61
+ end
62
+
63
+ pattern '/test$/' do
64
+ it { should match('/test$/') }
65
+ end
66
+
67
+ pattern '/te+st/' do
68
+ it { should match('/te+st/') }
69
+ it { should_not match('/test/') }
70
+ it { should_not match('/teest/') }
71
+ end
72
+
73
+ pattern "/path with spaces" do
74
+ it { should match('/path%20with%20spaces') }
75
+ it { should_not match('/path%2Bwith%2Bspaces') }
76
+ it { should_not match('/path+with+spaces') }
77
+ end
78
+
79
+ pattern '/foo&bar' do
80
+ it { should match('/foo&bar') }
81
+ end
82
+
83
+ pattern '/test.bar' do
84
+ it { should match('/test.bar') }
85
+ it { should_not match('/test0bar') }
86
+ end
87
+
88
+ pattern '/{foo,bar}' do
89
+ it(nil, skip: true) { should match('/foo') }
90
+ it(nil, skip: true) { should match('/bar') }
91
+ it { should_not match('/foobar') }
92
+ end
93
+
94
+ pattern '/foo/bar', uri_decode: false do
95
+ it { should match('/foo/bar') }
96
+ it { should_not match('/foo%2Fbar') }
97
+ it { should_not match('/foo%2fbar') }
98
+ end
99
+
100
+ pattern "/path with spaces", uri_decode: false do
101
+ it { should_not match('/path%20with%20spaces') }
102
+ it { should_not match('/path%2Bwith%2Bspaces') }
103
+ it { should_not match('/path+with+spaces') }
104
+ end
105
+
106
+ describe :=~ do
107
+ example { '/foo'.should be =~ Mustermann::Shell.new('/foo') }
108
+ end
109
+ end
@@ -0,0 +1,10 @@
1
+ require 'support'
2
+ require 'mustermann/simple_match'
3
+
4
+ describe Mustermann::SimpleMatch do
5
+ subject { Mustermann::SimpleMatch.new('example') }
6
+ its(:to_s) { should be == 'example' }
7
+ its(:names) { should be == [] }
8
+ its(:captures) { should be == [] }
9
+ example { subject[1].should be == nil }
10
+ end
@@ -0,0 +1,237 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require 'support'
3
+ require 'mustermann/simple'
4
+
5
+ describe Mustermann::Simple do
6
+ extend Support::Pattern
7
+
8
+ pattern '' do
9
+ it { should match('') }
10
+ it { should_not match('/') }
11
+ end
12
+
13
+ pattern '/' do
14
+ it { should match('/') }
15
+ it { should_not match('/foo') }
16
+ end
17
+
18
+ pattern '/foo' do
19
+ it { should match('/foo') }
20
+ it { should_not match('/bar') }
21
+ it { should_not match('/foo.bar') }
22
+ end
23
+
24
+ pattern '/foo/bar' do
25
+ it { should match('/foo/bar') }
26
+ it { should_not match('/foo%2Fbar') }
27
+ it { should_not match('/foo%2fbar') }
28
+ end
29
+
30
+ pattern '/:foo' do
31
+ it { should match('/foo') .capturing foo: 'foo' }
32
+ it { should match('/bar') .capturing foo: 'bar' }
33
+ it { should match('/foo.bar') .capturing foo: 'foo.bar' }
34
+ it { should match('/%0Afoo') .capturing foo: '%0Afoo' }
35
+ it { should match('/foo%2Fbar') .capturing foo: 'foo%2Fbar' }
36
+
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 "/:foo/:bar" do
48
+ it { should match('/foo/bar') .capturing foo: 'foo', bar: 'bar' }
49
+ it { should match('/foo.bar/bar.foo') .capturing foo: 'foo.bar', bar: 'bar.foo' }
50
+ it { should match('/user@example.com/name') .capturing foo: 'user@example.com', bar: 'name' }
51
+ it { should match('/10.1/te.st') .capturing foo: '10.1', bar: 'te.st' }
52
+ it { should match('/10.1.2/te.st') .capturing foo: '10.1.2', bar: 'te.st' }
53
+
54
+ it { should_not match('/foo%2Fbar') }
55
+ it { should_not match('/foo%2fbar') }
56
+
57
+ example { pattern.params('/bar/foo').should be == {"foo" => "bar", "bar" => "foo"} }
58
+ example { pattern.params('').should be_nil }
59
+ end
60
+
61
+ pattern '/hello/:person' do
62
+ it { should match('/hello/Frank').capturing person: 'Frank' }
63
+ end
64
+
65
+ pattern '/?:foo?/?:bar?' do
66
+ it { should match('/hello/world') .capturing foo: 'hello', bar: 'world' }
67
+ it { should match('/hello') .capturing foo: 'hello', bar: nil }
68
+ it { should match('/') .capturing foo: nil, bar: nil }
69
+ it { should match('') .capturing foo: nil, bar: nil }
70
+
71
+ it { should_not match('/hello/world/') }
72
+ end
73
+
74
+ pattern '/*' do
75
+ it { should match('/') .capturing splat: '' }
76
+ it { should match('/foo') .capturing splat: 'foo' }
77
+ it { should match('/foo/bar') .capturing splat: 'foo/bar' }
78
+
79
+ example { pattern.params('/foo').should be == {"splat" => ["foo"]} }
80
+ end
81
+
82
+ pattern '/:foo/*' do
83
+ it { should match("/foo/bar/baz") .capturing foo: 'foo', splat: 'bar/baz' }
84
+ it { should match("/foo/") .capturing foo: 'foo', splat: '' }
85
+ it { should match('/h%20w/h%20a%20y') .capturing foo: 'h%20w', splat: 'h%20a%20y' }
86
+ it { should_not match('/foo') }
87
+
88
+ example { pattern.params('/bar/foo').should be == {"splat" => ["foo"], "foo" => "bar"} }
89
+ example { pattern.params('/bar/foo/f%20o').should be == {"splat" => ["foo/f o"], "foo" => "bar"} }
90
+ end
91
+
92
+ pattern '/test$/' do
93
+ it { should match('/test$/') }
94
+ end
95
+
96
+ pattern '/te+st/' do
97
+ it { should match('/te+st/') }
98
+ it { should_not match('/test/') }
99
+ it { should_not match('/teest/') }
100
+ end
101
+
102
+ pattern "/path with spaces" do
103
+ it { should match('/path%20with%20spaces') }
104
+ it { should match('/path%2Bwith%2Bspaces') }
105
+ it { should match('/path+with+spaces') }
106
+ end
107
+
108
+ pattern '/foo&bar' do
109
+ it { should match('/foo&bar') }
110
+ end
111
+
112
+ pattern '/*/:foo/*/*' do
113
+ it { should match('/bar/foo/bling/baz/boom') }
114
+
115
+ it "should capture all splat parts" do
116
+ match = pattern.match('/bar/foo/bling/baz/boom')
117
+ match.captures.should be == ['bar', 'foo', 'bling', 'baz/boom']
118
+ match.names.should be == ['splat', 'foo']
119
+ end
120
+
121
+ it 'should map to proper params' do
122
+ pattern.params('/bar/foo/bling/baz/boom').should be == {
123
+ "foo" => "foo", "splat" => ['bar', 'bling', 'baz/boom']
124
+ }
125
+ end
126
+ end
127
+
128
+ pattern '/test.bar' do
129
+ it { should match('/test.bar') }
130
+ it { should_not match('/test0bar') }
131
+ end
132
+
133
+ pattern '/:file.:ext' do
134
+ it { should match('/pony.jpg') .capturing file: 'pony', ext: 'jpg' }
135
+ it { should match('/pony%2Ejpg') .capturing file: 'pony', ext: 'jpg' }
136
+ it { should match('/pony%2ejpg') .capturing file: 'pony', ext: 'jpg' }
137
+
138
+ it { should match('/pony%E6%AD%A3%2Ejpg') .capturing file: 'pony%E6%AD%A3', ext: 'jpg' }
139
+ it { should match('/pony%e6%ad%a3%2ejpg') .capturing file: 'pony%e6%ad%a3', ext: 'jpg' }
140
+ it { should match('/pony正%2Ejpg') .capturing file: 'pony正', ext: 'jpg' }
141
+ it { should match('/pony正%2ejpg') .capturing file: 'pony正', ext: 'jpg' }
142
+ it { should match('/pony正..jpg') .capturing file: 'pony正.', ext: 'jpg' }
143
+
144
+ it { should_not match('/.jpg') }
145
+ end
146
+
147
+ pattern '/:id/test.bar' do
148
+ it { should match('/3/test.bar') .capturing id: '3' }
149
+ it { should match('/2/test.bar') .capturing id: '2' }
150
+ it { should match('/2E/test.bar') .capturing id: '2E' }
151
+ it { should match('/2e/test.bar') .capturing id: '2e' }
152
+ it { should match('/%2E/test.bar') .capturing id: '%2E' }
153
+ end
154
+
155
+ pattern '/10/:id' do
156
+ it { should match('/10/test') .capturing id: 'test' }
157
+ it { should match('/10/te.st') .capturing id: 'te.st' }
158
+ end
159
+
160
+ pattern '/10.1/:id' do
161
+ it { should match('/10.1/test') .capturing id: 'test' }
162
+ it { should match('/10.1/te.st') .capturing id: 'te.st' }
163
+ end
164
+
165
+ pattern '/foo?' do
166
+ it { should match('/fo') }
167
+ it { should match('/foo') }
168
+ it { should_not match('') }
169
+ it { should_not match('/') }
170
+ it { should_not match('/f') }
171
+ it { should_not match('/fooo') }
172
+ end
173
+
174
+ pattern '/:fOO' do
175
+ it { should match('/a').capturing fOO: 'a' }
176
+ end
177
+
178
+ pattern '/:_X' do
179
+ it { should match('/a').capturing _X: 'a' }
180
+ end
181
+
182
+ pattern '/:f00' do
183
+ it { should match('/a').capturing f00: 'a' }
184
+ end
185
+
186
+ pattern '/:foo.?' do
187
+ it { should match('/a.').capturing foo: 'a.' }
188
+ it { should match('/xy').capturing foo: 'xy' }
189
+ end
190
+
191
+ pattern '/(a)' do
192
+ it { should match('/(a)') }
193
+ it { should_not match('/a') }
194
+ end
195
+
196
+ pattern '/:foo.?', greedy: false do
197
+ it { should match('/a.').capturing foo: 'a' }
198
+ it { should match('/xy').capturing foo: 'xy' }
199
+ end
200
+
201
+ pattern '/foo?', uri_decode: false do
202
+ it { should match('/foo') }
203
+ it { should match('/fo') }
204
+ it { should_not match('/foo?') }
205
+ end
206
+
207
+ pattern '/foo/bar', uri_decode: false do
208
+ it { should match('/foo/bar') }
209
+ it { should_not match('/foo%2Fbar') }
210
+ it { should_not match('/foo%2fbar') }
211
+ end
212
+
213
+ pattern "/path with spaces", uri_decode: false do
214
+ it { should match('/path with spaces') }
215
+ it { should_not match('/path%20with%20spaces') }
216
+ it { should_not match('/path%2Bwith%2Bspaces') }
217
+ it { should_not match('/path+with+spaces') }
218
+ end
219
+
220
+ pattern "/path with spaces", space_matches_plus: false do
221
+ it { should match('/path%20with%20spaces') }
222
+ it { should_not match('/path%2Bwith%2Bspaces') }
223
+ it { should_not match('/path+with+spaces') }
224
+ end
225
+
226
+ context 'error handling' do
227
+ example '? at beginning of route' do
228
+ expect { Mustermann::Simple.new('?foobar') }.
229
+ to raise_error(Mustermann::ParseError)
230
+ end
231
+
232
+ example 'invalid capture name' do
233
+ expect { Mustermann::Simple.new('/:1a/') }.
234
+ to raise_error(Mustermann::CompileError)
235
+ end
236
+ end
237
+ end