mustermann 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +2 -1
  3. data/.travis.yml +4 -3
  4. data/.yardopts +1 -0
  5. data/README.md +53 -10
  6. data/Rakefile +4 -1
  7. data/bench/capturing.rb +42 -9
  8. data/bench/template_vs_addressable.rb +3 -0
  9. data/internals.md +64 -0
  10. data/lib/mustermann.rb +14 -5
  11. data/lib/mustermann/ast/compiler.rb +150 -0
  12. data/lib/mustermann/ast/expander.rb +112 -0
  13. data/lib/mustermann/ast/node.rb +155 -0
  14. data/lib/mustermann/ast/parser.rb +136 -0
  15. data/lib/mustermann/ast/pattern.rb +89 -0
  16. data/lib/mustermann/ast/transformer.rb +121 -0
  17. data/lib/mustermann/ast/translator.rb +111 -0
  18. data/lib/mustermann/ast/tree_renderer.rb +29 -0
  19. data/lib/mustermann/ast/validation.rb +40 -0
  20. data/lib/mustermann/error.rb +4 -12
  21. data/lib/mustermann/extension.rb +3 -6
  22. data/lib/mustermann/identity.rb +4 -4
  23. data/lib/mustermann/pattern.rb +34 -5
  24. data/lib/mustermann/rails.rb +7 -16
  25. data/lib/mustermann/regexp_based.rb +4 -4
  26. data/lib/mustermann/shell.rb +4 -4
  27. data/lib/mustermann/simple.rb +1 -1
  28. data/lib/mustermann/simple_match.rb +2 -2
  29. data/lib/mustermann/sinatra.rb +10 -20
  30. data/lib/mustermann/template.rb +11 -104
  31. data/lib/mustermann/version.rb +1 -1
  32. data/mustermann.gemspec +1 -1
  33. data/spec/extension_spec.rb +143 -0
  34. data/spec/mustermann_spec.rb +41 -0
  35. data/spec/pattern_spec.rb +16 -6
  36. data/spec/rails_spec.rb +77 -9
  37. data/spec/sinatra_spec.rb +6 -0
  38. data/spec/support.rb +5 -78
  39. data/spec/support/coverage.rb +18 -0
  40. data/spec/support/env.rb +6 -0
  41. data/spec/support/expand_matcher.rb +27 -0
  42. data/spec/support/match_matcher.rb +39 -0
  43. data/spec/support/pattern.rb +28 -0
  44. metadata +43 -43
  45. data/.test_queue_stats +0 -0
  46. data/lib/mustermann/ast.rb +0 -403
  47. data/spec/ast_spec.rb +0 -8
@@ -2,15 +2,25 @@ require 'support'
2
2
  require 'mustermann/pattern'
3
3
 
4
4
  describe Mustermann::Pattern do
5
- it 'raises a NotImplementedError when used directly' do
6
- expect { described_class.new("") === "" }.to raise_error(NotImplementedError)
5
+ describe :=== do
6
+ it 'raises a NotImplementedError when used directly' do
7
+ expect { described_class.new("") === "" }.to raise_error(NotImplementedError)
8
+ end
7
9
  end
8
10
 
9
- it 'raises an ArgumentError for unknown options' do
10
- expect { described_class.new("", foo: :bar) }.to raise_error(ArgumentError)
11
+ describe :initialize do
12
+ it 'raises an ArgumentError for unknown options' do
13
+ expect { described_class.new("", foo: :bar) }.to raise_error(ArgumentError)
14
+ end
15
+
16
+ it 'does not complain about unknown options if ignore_unknown_options is enabled' do
17
+ expect { described_class.new("", foo: :bar, ignore_unknown_options: true) }.not_to raise_error
18
+ end
11
19
  end
12
20
 
13
- it 'does not complain about unknown options if ignore_unknown_options is enabled' do
14
- expect { described_class.new("", foo: :bar, ignore_unknown_options: true) }.not_to raise_error
21
+ describe :expand do
22
+ subject(:pattern) { described_class.new("") }
23
+ it { should_not respond_to(:expand) }
24
+ it { expect { pattern.expand }.to raise_error(NotImplementedError) }
15
25
  end
16
26
  end
@@ -7,23 +7,35 @@ describe Mustermann::Rails do
7
7
  pattern '' do
8
8
  it { should match('') }
9
9
  it { should_not match('/') }
10
+
11
+ it { should expand.to('') }
12
+ it { should_not expand(a: 1) }
10
13
  end
11
14
 
12
15
  pattern '/' do
13
16
  it { should match('/') }
14
17
  it { should_not match('/foo') }
18
+
19
+ it { should expand.to('/') }
20
+ it { should_not expand(a: 1) }
15
21
  end
16
22
 
17
23
  pattern '/foo' do
18
24
  it { should match('/foo') }
19
25
  it { should_not match('/bar') }
20
26
  it { should_not match('/foo.bar') }
27
+
28
+ it { should expand.to('/foo') }
29
+ it { should_not expand(a: 1) }
21
30
  end
22
31
 
23
32
  pattern '/foo/bar' do
24
33
  it { should match('/foo/bar') }
25
34
  it { should_not match('/foo%2Fbar') }
26
35
  it { should_not match('/foo%2fbar') }
36
+
37
+ it { should expand.to('/foo/bar') }
38
+ it { should_not expand(a: 1) }
27
39
  end
28
40
 
29
41
  pattern '/:foo' do
@@ -38,13 +50,23 @@ describe Mustermann::Rails do
38
50
  it { should_not match('/') }
39
51
  it { should_not match('/foo/') }
40
52
 
41
- example { pattern.params('/foo').should be == {"foo" => "foo"} }
42
- example { pattern.params('/f%20o').should be == {"foo" => "f o"} }
53
+ example { pattern.params('/foo') .should be == {"foo" => "foo"} }
54
+ example { pattern.params('/f%20o') .should be == {"foo" => "f o"} }
43
55
  example { pattern.params('').should be_nil }
56
+
57
+ it { should expand(foo: 'bar') .to('/bar') }
58
+ it { should expand(foo: 'b r') .to('/b%20r') }
59
+ it { should expand(foo: 'foo/bar') .to('/foo%2Fbar') }
60
+
61
+ it { should_not expand(foo: 'foo', bar: 'bar') }
62
+ it { should_not expand(bar: 'bar') }
63
+ it { should_not expand }
44
64
  end
45
65
 
46
66
  pattern '/föö' do
47
67
  it { should match("/f%C3%B6%C3%B6") }
68
+ it { should expand.to("/f%C3%B6%C3%B6") }
69
+ it { should_not expand(a: 1) }
48
70
  end
49
71
 
50
72
  pattern "/:foo/:bar" do
@@ -59,48 +81,72 @@ describe Mustermann::Rails do
59
81
 
60
82
  example { pattern.params('/bar/foo').should be == {"foo" => "bar", "bar" => "foo"} }
61
83
  example { pattern.params('').should be_nil }
84
+
85
+ it { should expand(foo: 'foo', bar: 'bar').to('/foo/bar') }
86
+ it { should_not expand(foo: 'foo') }
87
+ it { should_not expand(bar: 'bar') }
62
88
  end
63
89
 
64
90
  pattern '/hello/:person' do
65
91
  it { should match('/hello/Frank').capturing person: 'Frank' }
92
+ it { should expand(person: 'Frank') .to '/hello/Frank' }
93
+ it { should expand(person: 'Frank?') .to '/hello/Frank%3F' }
66
94
  end
67
95
 
68
96
  pattern '/?:foo?/?:bar?' do
69
97
  it { should match('/?hello?/?world?').capturing foo: 'hello', bar: 'world' }
70
98
  it { should_not match('/hello/world/') }
99
+ it { should expand(foo: 'hello', bar: 'world').to('/%3Fhello%3F/%3Fworld%3F') }
71
100
  end
72
101
 
73
102
  pattern '/:foo_bar' do
74
103
  it { should match('/hello').capturing foo_bar: 'hello' }
104
+ it { should expand(foo_bar: 'hello').to('/hello') }
75
105
  end
76
106
 
77
107
  pattern '/*foo' do
78
108
  it { should match('/') .capturing foo: '' }
79
109
  it { should match('/foo') .capturing foo: 'foo' }
80
110
  it { should match('/foo/bar') .capturing foo: 'foo/bar' }
111
+
112
+ it { should expand .to('/') }
113
+ it { should expand(foo: nil) .to('/') }
114
+ it { should expand(foo: '') .to('/') }
115
+ it { should expand(foo: 'foo') .to('/foo') }
116
+ it { should expand(foo: 'foo/bar') .to('/foo/bar') }
117
+ it { should expand(foo: 'foo.bar') .to('/foo.bar') }
81
118
  end
82
119
 
83
120
  pattern '/:foo/*bar' do
84
- it { should match("/foo/bar/baz") .capturing foo: 'foo', bar: 'bar/baz' }
85
- it { should match("/foo/") .capturing foo: 'foo', bar: '' }
86
- it { should match('/h%20w/h%20a%20y') .capturing foo: 'h%20w', bar: 'h%20a%20y' }
121
+ it { should match("/foo/bar/baz") .capturing foo: 'foo', bar: 'bar/baz' }
122
+ it { should match("/foo%2Fbar/baz") .capturing foo: 'foo%2Fbar', bar: 'baz' }
123
+ it { should match("/foo/") .capturing foo: 'foo', bar: '' }
124
+ it { should match('/h%20w/h%20a%20y') .capturing foo: 'h%20w', bar: 'h%20a%20y' }
87
125
  it { should_not match('/foo') }
126
+
127
+ it { should expand(foo: 'foo') .to('/foo/') }
128
+ it { should expand(foo: 'foo', bar: 'bar') .to('/foo/bar') }
129
+ it { should expand(foo: 'foo', bar: 'foo/bar') .to('/foo/foo/bar') }
130
+ it { should expand(foo: 'foo/bar', bar: 'bar') .to('/foo%2Fbar/bar') }
88
131
  end
89
132
 
90
133
  pattern '/test$/' do
91
134
  it { should match('/test$/') }
135
+ it { should expand.to('/test$/') }
92
136
  end
93
137
 
94
138
  pattern '/te+st/' do
95
139
  it { should match('/te+st/') }
96
140
  it { should_not match('/test/') }
97
141
  it { should_not match('/teest/') }
142
+ it { should expand.to('/te+st/') }
98
143
  end
99
144
 
100
145
  pattern "/path with spaces" do
101
- it { should match('/path%20with%20spaces') }
102
- it { should match('/path%2Bwith%2Bspaces') }
103
- it { should match('/path+with+spaces') }
146
+ it { should match('/path%20with%20spaces') }
147
+ it { should match('/path%2Bwith%2Bspaces') }
148
+ it { should match('/path+with+spaces') }
149
+ it { should expand.to('/path%20with%20spaces') }
104
150
  end
105
151
 
106
152
  pattern '/foo&bar' do
@@ -110,6 +156,7 @@ describe Mustermann::Rails do
110
156
  pattern '/*a/:foo/*b/*c' do
111
157
  it { should match('/bar/foo/bling/baz/boom').capturing a: 'bar', foo: 'foo', b: 'bling', c: 'baz/boom' }
112
158
  example { pattern.params('/bar/foo/bling/baz/boom').should be == { "a" => 'bar', "foo" => 'foo', "b" => 'bling', "c" => 'baz/boom' } }
159
+ it { should expand(a: 'bar', foo: 'foo', b: 'bling', c: 'baz/boom').to('/bar/foo/bling/baz/boom') }
113
160
  end
114
161
 
115
162
  pattern '/test.bar' do
@@ -118,7 +165,7 @@ describe Mustermann::Rails do
118
165
  end
119
166
 
120
167
  pattern '/:file.:ext' do
121
- it { should match('/pony.jpg') .capturing file: 'pony', ext: 'jpg' }
168
+ it { should match('/pony.jpg') .capturing file: 'pony', ext: 'jpg' }
122
169
  it { should match('/pony%2Ejpg') .capturing file: 'pony', ext: 'jpg' }
123
170
  it { should match('/pony%2ejpg') .capturing file: 'pony', ext: 'jpg' }
124
171
 
@@ -129,6 +176,7 @@ describe Mustermann::Rails do
129
176
  it { should match('/pony正..jpg') .capturing file: 'pony正.', ext: 'jpg' }
130
177
 
131
178
  it { should_not match('/.jpg') }
179
+ it { should expand(file: 'pony', ext: 'jpg').to('/pony.jpg') }
132
180
  end
133
181
 
134
182
  pattern '/:a(x)' do
@@ -138,12 +186,17 @@ describe Mustermann::Rails do
138
186
  it { should match('/ax') .capturing a: 'a' }
139
187
  it { should match('/axax') .capturing a: 'axa' }
140
188
  it { should match('/axaxx') .capturing a: 'axax' }
189
+ it { should expand(a: 'x').to('/xx') }
190
+ it { should expand(a: 'a').to('/ax') }
141
191
  end
142
192
 
143
193
  pattern '/:user(@:host)' do
144
194
  it { should match('/foo@bar') .capturing user: 'foo', host: 'bar' }
145
195
  it { should match('/foo.foo@bar') .capturing user: 'foo.foo', host: 'bar' }
146
196
  it { should match('/foo@bar.bar') .capturing user: 'foo', host: 'bar.bar' }
197
+
198
+ it { should expand(user: 'foo') .to('/foo') }
199
+ it { should expand(user: 'foo', host: 'bar') .to('/foo@bar') }
147
200
  end
148
201
 
149
202
  pattern '/:file(.:ext)' do
@@ -154,6 +207,9 @@ describe Mustermann::Rails do
154
207
  it { should match('/pony.png.jpg') .capturing file: 'pony.png', ext: 'jpg' }
155
208
  it { should match('/pony.') .capturing file: 'pony.' }
156
209
  it { should_not match('/.jpg') }
210
+
211
+ it { should expand(file: 'pony') .to('/pony') }
212
+ it { should expand(file: 'pony', ext: 'jpg') .to('/pony.jpg') }
157
213
  end
158
214
 
159
215
  pattern '/:id/test.bar' do
@@ -185,6 +241,9 @@ describe Mustermann::Rails do
185
241
  it { should match('/a.b/c') .capturing a: 'a.b', b: 'c', c: nil }
186
242
  it { should match('/a.b/c.d') .capturing a: 'a.b', b: 'c', c: 'd' }
187
243
  it { should_not match('/a.b/c.d/e') }
244
+
245
+ it { should expand(a: ?a, b: ?b) .to('/a/b.') }
246
+ it { should expand(a: ?a, b: ?b, c: ?c) .to('/a/b.c') }
188
247
  end
189
248
 
190
249
  pattern '/:a(foo:b)' do
@@ -192,6 +251,9 @@ describe Mustermann::Rails do
192
251
  it { should match('/barfoobarfoobar') .capturing a: 'barfoobar', b: 'bar' }
193
252
  it { should match('/bar') .capturing a: 'bar', b: nil }
194
253
  it { should_not match('/') }
254
+
255
+ it { should expand(a: ?a) .to('/a') }
256
+ it { should expand(a: ?a, b: ?b) .to('/afoob') }
195
257
  end
196
258
 
197
259
  pattern '/fo(o)' do
@@ -201,6 +263,8 @@ describe Mustermann::Rails do
201
263
  it { should_not match('/') }
202
264
  it { should_not match('/f') }
203
265
  it { should_not match('/fooo') }
266
+
267
+ it { should expand.to('/foo') }
204
268
  end
205
269
 
206
270
  pattern '/foo?' do
@@ -212,6 +276,8 @@ describe Mustermann::Rails do
212
276
  it { should_not match('/') }
213
277
  it { should_not match('/f') }
214
278
  it { should_not match('/fooo') }
279
+
280
+ it { should expand.to('/foo%3F') }
215
281
  end
216
282
 
217
283
  pattern '/:fOO' do
@@ -228,6 +294,8 @@ describe Mustermann::Rails do
228
294
 
229
295
  pattern '/:foo(/:bar)/:baz' do
230
296
  it { should match('/foo/bar/baz').capturing foo: 'foo', bar: 'bar', baz: 'baz' }
297
+ it { should expand(foo: ?a, baz: ?b) .to('/a/b') }
298
+ it { should expand(foo: ?a, baz: ?b, bar: ?x) .to('/a/x/b') }
231
299
  end
232
300
 
233
301
  pattern '/:foo', capture: /\d+/ do
@@ -74,6 +74,12 @@ describe Mustermann::Sinatra do
74
74
  it { should match('') .capturing foo: nil, bar: nil }
75
75
 
76
76
  it { should_not match('/hello/world/') }
77
+
78
+ # it { should expand(foo: 'hello') .to('/hello') }
79
+ # it { should expand(foo: 'hello', bar: 'world') .to('/hello/world') }
80
+ # it { should expand(bar: 'world') .to('//world') }
81
+ # it { should expand .to('') }
82
+ # it { should_not expand(baz: '') }
77
83
  end
78
84
 
79
85
  pattern '/:foo_bar' do
@@ -1,78 +1,5 @@
1
- ENV['RACK_ENV'] = 'test'
2
-
3
- require 'simplecov'
4
- require 'coveralls'
5
-
6
- SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
7
- SimpleCov::Formatter::HTMLFormatter,
8
- Coveralls::SimpleCov::Formatter
9
- ]
10
-
11
- SimpleCov.start do
12
- project_name 'mustermann'
13
- minimum_coverage 100
14
-
15
- add_filter "/spec/"
16
- add_group 'Library', 'lib'
17
- end
18
-
19
- RSpec::Matchers.define :match do |expected|
20
- match do |actual|
21
- @captures ||= false
22
- match = actual.match(expected)
23
- match &&= @captures.all? { |k, v| match[k] == v } if @captures
24
- match
25
- end
26
-
27
- chain :capturing do |captures|
28
- @captures = captures
29
- end
30
-
31
- failure_message_for_should do |actual|
32
- require 'pp'
33
- match = actual.match(expected)
34
- if match
35
- key, value = @captures.detect { |k, v| match[k] != v }
36
- "expected %p to capture %p as %p when matching %p, but got %p\n\nRegular Expression:\n%p" % [
37
- actual.to_s, key, value, expected, match[key], actual.regexp
38
- ]
39
- else
40
- "expected %p to match %p" % [ actual, expected ]
41
- end
42
- end
43
-
44
- failure_message_for_should_not do |actual|
45
- "expected %p not to match %p" % [
46
- actual.to_s, expected
47
- ]
48
- end
49
- end
50
-
51
- module Support
52
- module Pattern
53
- def pattern(pattern, options = nil, &block)
54
- description = "pattern %p" % pattern
55
-
56
- if options
57
- description << " with options %p" % options
58
- instance = described_class.new(pattern, options)
59
- else
60
- instance = described_class.new(pattern)
61
- end
62
-
63
- context description do
64
- subject(:pattern) { instance }
65
- its(:to_s) { should be == pattern }
66
- its(:inspect) { should be == "#<#{described_class}:#{pattern.inspect}>" }
67
- its(:names) { should be_an(Array) }
68
-
69
- example 'string should be immune to external change' do
70
- subject.to_s.replace "NOT THE PATTERN"
71
- subject.to_s.should be == pattern
72
- end
73
-
74
- instance_eval(&block)
75
- end
76
- end
77
- end
78
- end
1
+ require 'support/env'
2
+ require 'support/coverage'
3
+ require 'support/expand_matcher'
4
+ require 'support/match_matcher'
5
+ require 'support/pattern'
@@ -0,0 +1,18 @@
1
+ if RUBY_ENGINE == 'ruby' and RUBY_VERSION < '2.1'
2
+ require 'simplecov'
3
+ require 'coveralls'
4
+
5
+ SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
6
+ SimpleCov::Formatter::HTMLFormatter,
7
+ Coveralls::SimpleCov::Formatter
8
+ ]
9
+
10
+ SimpleCov.start do
11
+ project_name 'mustermann'
12
+ minimum_coverage 100
13
+ coverage_dir '.coverage'
14
+
15
+ add_filter "/spec/"
16
+ add_group 'Library', 'lib'
17
+ end
18
+ end
@@ -0,0 +1,6 @@
1
+ if RUBY_VERSION < '2.0.0'
2
+ $stderr.puts "needs Ruby 2.0.0, you're running #{RUBY_VERSION}"
3
+ exit 1
4
+ end
5
+
6
+ ENV['RACK_ENV'] = 'test'
@@ -0,0 +1,27 @@
1
+ RSpec::Matchers.define :expand do |values = {}|
2
+ match do |pattern|
3
+ @string ||= nil
4
+ begin
5
+ expanded = pattern.expand(values)
6
+ rescue Exception
7
+ false
8
+ else
9
+ @string ? @string == expanded : !!expanded
10
+ end
11
+ end
12
+
13
+ chain :to do |string|
14
+ @string = string
15
+ end
16
+
17
+ failure_message_for_should do |pattern|
18
+ message = "expected %p to be expandable with %p" % [pattern, values]
19
+ expanded = pattern.expand(values)
20
+ message << " and result in %p, but got %p" % [@string, expanded] if @string
21
+ message
22
+ end
23
+
24
+ failure_message_for_should_not do |pattern|
25
+ "expected %p not to be expandable with %p" % [pattern, values]
26
+ end
27
+ end
@@ -0,0 +1,39 @@
1
+ RSpec::Matchers.define :match do |expected|
2
+ match do |actual|
3
+ @captures ||= false
4
+ match = actual.match(expected)
5
+ match &&= @captures.all? { |k, v| match[k] == v } if @captures
6
+ match
7
+ end
8
+
9
+ chain :capturing do |captures|
10
+ @captures = captures
11
+ end
12
+
13
+ failure_message_for_should do |actual|
14
+ require 'pp'
15
+ match = actual.match(expected)
16
+ if match
17
+ key, value = @captures.detect { |k, v| match[k] != v }
18
+ message = "expected %p to capture %p as %p when matching %p, but got %p\n\nRegular Expression:\n%p" % [
19
+ actual.to_s, key, value, expected, match[key], actual.regexp
20
+ ]
21
+
22
+ if ast = actual.instance_variable_get(:@ast)
23
+ require 'mustermann/ast/tree_renderer'
24
+ tree = Mustermann::AST::TreeRenderer.render(ast)
25
+ message << "\n\nAST:\n#{tree}"
26
+ end
27
+
28
+ message
29
+ else
30
+ "expected %p to match %p" % [ actual, expected ]
31
+ end
32
+ end
33
+
34
+ failure_message_for_should_not do |actual|
35
+ "expected %p not to match %p" % [
36
+ actual.to_s, expected
37
+ ]
38
+ end
39
+ end