mustermann 0.0.1 → 0.1.0

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