mustermann 0.0.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.
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/.test_queue_stats +0 -0
- data/.travis.yml +6 -0
- data/Gemfile +2 -0
- data/LICENSE +22 -0
- data/README.md +644 -0
- data/Rakefile +3 -0
- data/bench/capturing.rb +24 -0
- data/bench/simple_vs_sinatra.rb +23 -0
- data/bench/template_vs_addressable.rb +23 -0
- data/lib/mustermann.rb +40 -0
- data/lib/mustermann/ast.rb +403 -0
- data/lib/mustermann/error.rb +14 -0
- data/lib/mustermann/extension.rb +52 -0
- data/lib/mustermann/identity.rb +19 -0
- data/lib/mustermann/pattern.rb +142 -0
- data/lib/mustermann/rails.rb +26 -0
- data/lib/mustermann/regexp_based.rb +30 -0
- data/lib/mustermann/shell.rb +22 -0
- data/lib/mustermann/simple.rb +35 -0
- data/lib/mustermann/simple_match.rb +30 -0
- data/lib/mustermann/sinatra.rb +32 -0
- data/lib/mustermann/template.rb +140 -0
- data/lib/mustermann/version.rb +3 -0
- data/mustermann.gemspec +28 -0
- data/spec/ast_spec.rb +8 -0
- data/spec/extension_spec.rb +153 -0
- data/spec/identity_spec.rb +82 -0
- data/spec/mustermann_spec.rb +0 -0
- data/spec/pattern_spec.rb +16 -0
- data/spec/rails_spec.rb +453 -0
- data/spec/regexp_based_spec.rb +8 -0
- data/spec/shell_spec.rb +108 -0
- data/spec/simple_match_spec.rb +10 -0
- data/spec/simple_spec.rb +236 -0
- data/spec/sinatra_spec.rb +554 -0
- data/spec/support.rb +78 -0
- data/spec/template_spec.rb +814 -0
- metadata +227 -0
data/mustermann.gemspec
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
$:.unshift File.expand_path("../lib", __FILE__)
|
2
|
+
require "mustermann/version"
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = "mustermann"
|
6
|
+
s.version = Mustermann::VERSION
|
7
|
+
s.author = "Konstantin Haase"
|
8
|
+
s.email = "konstantin.mailinglists@googlemail.com"
|
9
|
+
s.homepage = "https://github.com/rkh/mustermann"
|
10
|
+
s.summary = %q{use patterns like regular expressions}
|
11
|
+
s.description = %q{library implementing patterns that behave like regular expressions}
|
12
|
+
s.license = 'MIT'
|
13
|
+
s.files = `git ls-files`.split("\n")
|
14
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
15
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
16
|
+
s.extra_rdoc_files = %w[README.md]
|
17
|
+
s.require_path = 'lib'
|
18
|
+
s.required_ruby_version = '>= 2.0.0'
|
19
|
+
|
20
|
+
s.add_development_dependency 'rspec'
|
21
|
+
s.add_development_dependency 'sinatra', '~> 1.4'
|
22
|
+
s.add_development_dependency 'rack-test'
|
23
|
+
s.add_development_dependency 'rake'
|
24
|
+
s.add_development_dependency 'yard'
|
25
|
+
s.add_development_dependency 'redcarpet'
|
26
|
+
s.add_development_dependency 'simplecov'
|
27
|
+
s.add_development_dependency 'coveralls'
|
28
|
+
end
|
data/spec/ast_spec.rb
ADDED
@@ -0,0 +1,153 @@
|
|
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.get('/:id', capture: /\d+/) { 'ok' }
|
48
|
+
end
|
49
|
+
|
50
|
+
example { get('/42').should be_ok }
|
51
|
+
example { get('/foo').should_not be_ok }
|
52
|
+
end
|
53
|
+
|
54
|
+
context 'global and route local' do
|
55
|
+
context 'global is a hash' do
|
56
|
+
before do
|
57
|
+
app.set(:pattern, capture: { id: /\d+/ })
|
58
|
+
app.get('/:id(.:ext)?', capture: { ext: 'png' }) { ?a }
|
59
|
+
app.get('/:id', capture: { id: 'foo' }) { ?b }
|
60
|
+
app.get('/:id', capture: :alpha) { ?c }
|
61
|
+
end
|
62
|
+
|
63
|
+
example { get('/20') .body.should be == ?a }
|
64
|
+
example { get('/20.png') .body.should be == ?a }
|
65
|
+
example { get('/foo') .body.should be == ?b }
|
66
|
+
example { get('/bar') .body.should be == ?c }
|
67
|
+
end
|
68
|
+
|
69
|
+
context 'global is not a hash' do
|
70
|
+
before do
|
71
|
+
app.set(:pattern, capture: /\d+/)
|
72
|
+
app.get('/:slug(.:ext)?', capture: { ext: 'png' }) { params[:slug] }
|
73
|
+
app.get('/:slug', capture: :alpha) { 'ok' }
|
74
|
+
end
|
75
|
+
|
76
|
+
example { get('/20.png').should be_ok }
|
77
|
+
example { get('/foo.png').should_not be_ok }
|
78
|
+
example { get('/foo').should be_ok }
|
79
|
+
|
80
|
+
example { get('/20.png') .body.should be == '20' }
|
81
|
+
example { get('/42') .body.should be == '42' }
|
82
|
+
example { get('/foo') .body.should be == 'ok' }
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
describe :type do
|
88
|
+
describe :identity do
|
89
|
+
before do
|
90
|
+
app.set(:pattern, type: :identity)
|
91
|
+
app.get('/:foo') { 'ok' }
|
92
|
+
end
|
93
|
+
|
94
|
+
example { get('/:foo').should be_ok }
|
95
|
+
example { get('/foo').should_not be_ok }
|
96
|
+
end
|
97
|
+
|
98
|
+
describe :rails do
|
99
|
+
before do
|
100
|
+
app.set(:pattern, type: :rails)
|
101
|
+
app.get('/:slug(.:extension)') { params[:slug] }
|
102
|
+
end
|
103
|
+
|
104
|
+
example { get('/foo') .body.should be == 'foo' }
|
105
|
+
example { get('/foo.') .body.should be == 'foo.' }
|
106
|
+
example { get('/foo.bar') .body.should be == 'foo' }
|
107
|
+
example { get('/a%20b') .body.should be == 'a b' }
|
108
|
+
end
|
109
|
+
|
110
|
+
describe :shell do
|
111
|
+
before do
|
112
|
+
app.set(:pattern, type: :shell)
|
113
|
+
app.get('/{foo,bar}') { 'ok' }
|
114
|
+
end
|
115
|
+
|
116
|
+
example { get('/foo').should be_ok }
|
117
|
+
example { get('/bar').should be_ok }
|
118
|
+
end
|
119
|
+
|
120
|
+
describe :simple do
|
121
|
+
before do
|
122
|
+
app.set(:pattern, type: :simple)
|
123
|
+
app.get('/(a)') { 'ok' }
|
124
|
+
end
|
125
|
+
|
126
|
+
example { get('/(a)').should be_ok }
|
127
|
+
example { get('/a').should_not be_ok }
|
128
|
+
end
|
129
|
+
|
130
|
+
describe :simple do
|
131
|
+
before do
|
132
|
+
app.set(:pattern, type: :template)
|
133
|
+
app.get('/foo{/segments*}{.ext}') { "%p %p" % [params[:segments], params[:ext]] }
|
134
|
+
end
|
135
|
+
|
136
|
+
example { get('/foo/a.png').should be_ok }
|
137
|
+
example { get('/foo/a').should_not be_ok }
|
138
|
+
|
139
|
+
example { get('/foo/a.png').body.should be == '["a"] "png"' }
|
140
|
+
example { get('/foo/a/b.png').body.should be == '["a", "b"] "png"' }
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
context 'works with filters' do
|
145
|
+
before do
|
146
|
+
app.before('/auth/*', except: '/auth/login') { halt 'auth required' }
|
147
|
+
app.get('/auth/login') { 'please log in' }
|
148
|
+
end
|
149
|
+
|
150
|
+
example { get('/auth/dunno').body.should be == 'auth required' }
|
151
|
+
example { get('/auth/login').body.should be == 'please log in' }
|
152
|
+
end
|
153
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'support'
|
2
|
+
require 'mustermann/identity'
|
3
|
+
|
4
|
+
describe Mustermann::Identity 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
|
+
|
16
|
+
example { pattern.params('/').should be == {} }
|
17
|
+
example { pattern.params('').should be_nil }
|
18
|
+
end
|
19
|
+
|
20
|
+
pattern '/foo' do
|
21
|
+
it { should match('/foo') }
|
22
|
+
it { should_not match('/bar') }
|
23
|
+
it { should_not match('/foo.bar') }
|
24
|
+
end
|
25
|
+
|
26
|
+
pattern '/foo/bar' do
|
27
|
+
it { should match('/foo/bar') }
|
28
|
+
it { should match('/foo%2Fbar') }
|
29
|
+
it { should match('/foo%2fbar') }
|
30
|
+
end
|
31
|
+
|
32
|
+
pattern '/:foo' do
|
33
|
+
it { should match('/:foo') }
|
34
|
+
it { should match('/%3Afoo') }
|
35
|
+
it { should_not match('/foo') }
|
36
|
+
it { should_not match('/foo?') }
|
37
|
+
it { should_not match('/foo/bar') }
|
38
|
+
it { should_not match('/') }
|
39
|
+
it { should_not match('/foo/') }
|
40
|
+
end
|
41
|
+
|
42
|
+
pattern '/föö' do
|
43
|
+
it { should match("/f%C3%B6%C3%B6") }
|
44
|
+
end
|
45
|
+
|
46
|
+
pattern '/test$/' do
|
47
|
+
it { should match('/test$/') }
|
48
|
+
end
|
49
|
+
|
50
|
+
pattern '/te+st/' do
|
51
|
+
it { should match('/te+st/') }
|
52
|
+
it { should_not match('/test/') }
|
53
|
+
it { should_not match('/teest/') }
|
54
|
+
end
|
55
|
+
|
56
|
+
pattern "/path with spaces" do
|
57
|
+
it { should match('/path%20with%20spaces') }
|
58
|
+
it { should_not match('/path%2Bwith%2Bspaces') }
|
59
|
+
it { should_not match('/path+with+spaces') }
|
60
|
+
end
|
61
|
+
|
62
|
+
pattern '/foo&bar' do
|
63
|
+
it { should match('/foo&bar') }
|
64
|
+
end
|
65
|
+
|
66
|
+
pattern '/test.bar' do
|
67
|
+
it { should match('/test.bar') }
|
68
|
+
it { should_not match('/test0bar') }
|
69
|
+
end
|
70
|
+
|
71
|
+
pattern '/foo/bar', uri_decode: false do
|
72
|
+
it { should match('/foo/bar') }
|
73
|
+
it { should_not match('/foo%2Fbar') }
|
74
|
+
it { should_not match('/foo%2fbar') }
|
75
|
+
end
|
76
|
+
|
77
|
+
pattern "/path with spaces", uri_decode: false do
|
78
|
+
it { should_not match('/path%20with%20spaces') }
|
79
|
+
it { should_not match('/path%2Bwith%2Bspaces') }
|
80
|
+
it { should_not match('/path+with+spaces') }
|
81
|
+
end
|
82
|
+
end
|
File without changes
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'support'
|
2
|
+
require 'mustermann/pattern'
|
3
|
+
|
4
|
+
describe Mustermann::Pattern do
|
5
|
+
it 'raises a NotImplementedError when used directly' do
|
6
|
+
expect { described_class.new("") === "" }.to raise_error(NotImplementedError)
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'raises an ArgumentError for unknown options' do
|
10
|
+
expect { described_class.new("", foo: :bar) }.to raise_error(ArgumentError)
|
11
|
+
end
|
12
|
+
|
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
|
15
|
+
end
|
16
|
+
end
|
data/spec/rails_spec.rb
ADDED
@@ -0,0 +1,453 @@
|
|
1
|
+
require 'support'
|
2
|
+
require 'mustermann/rails'
|
3
|
+
|
4
|
+
describe Mustermann::Rails 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
|
+
|
36
|
+
it { should_not match('/foo?') }
|
37
|
+
it { should_not match('/foo/bar') }
|
38
|
+
it { should_not match('/') }
|
39
|
+
it { should_not match('/foo/') }
|
40
|
+
|
41
|
+
example { pattern.params('/foo').should be == {"foo" => "foo"} }
|
42
|
+
example { pattern.params('/f%20o').should be == {"foo" => "f o"} }
|
43
|
+
example { pattern.params('').should be_nil }
|
44
|
+
end
|
45
|
+
|
46
|
+
pattern '/föö' do
|
47
|
+
it { should match("/f%C3%B6%C3%B6") }
|
48
|
+
end
|
49
|
+
|
50
|
+
pattern "/:foo/:bar" do
|
51
|
+
it { should match('/foo/bar') .capturing foo: 'foo', bar: 'bar' }
|
52
|
+
it { should match('/foo.bar/bar.foo') .capturing foo: 'foo.bar', bar: 'bar.foo' }
|
53
|
+
it { should match('/user@example.com/name') .capturing foo: 'user@example.com', bar: 'name' }
|
54
|
+
it { should match('/10.1/te.st') .capturing foo: '10.1', bar: 'te.st' }
|
55
|
+
it { should match('/10.1.2/te.st') .capturing foo: '10.1.2', bar: 'te.st' }
|
56
|
+
|
57
|
+
it { should_not match('/foo%2Fbar') }
|
58
|
+
it { should_not match('/foo%2fbar') }
|
59
|
+
|
60
|
+
example { pattern.params('/bar/foo').should be == {"foo" => "bar", "bar" => "foo"} }
|
61
|
+
example { pattern.params('').should be_nil }
|
62
|
+
end
|
63
|
+
|
64
|
+
pattern '/hello/:person' do
|
65
|
+
it { should match('/hello/Frank').capturing person: 'Frank' }
|
66
|
+
end
|
67
|
+
|
68
|
+
pattern '/?:foo?/?:bar?' do
|
69
|
+
it { should match('/?hello?/?world?').capturing foo: 'hello', bar: 'world' }
|
70
|
+
it { should_not match('/hello/world/') }
|
71
|
+
end
|
72
|
+
|
73
|
+
pattern '/:foo_bar' do
|
74
|
+
it { should match('/hello').capturing foo_bar: 'hello' }
|
75
|
+
end
|
76
|
+
|
77
|
+
pattern '/*foo' do
|
78
|
+
it { should match('/') .capturing foo: '' }
|
79
|
+
it { should match('/foo') .capturing foo: 'foo' }
|
80
|
+
it { should match('/foo/bar') .capturing foo: 'foo/bar' }
|
81
|
+
end
|
82
|
+
|
83
|
+
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' }
|
87
|
+
it { should_not match('/foo') }
|
88
|
+
end
|
89
|
+
|
90
|
+
pattern '/test$/' do
|
91
|
+
it { should match('/test$/') }
|
92
|
+
end
|
93
|
+
|
94
|
+
pattern '/te+st/' do
|
95
|
+
it { should match('/te+st/') }
|
96
|
+
it { should_not match('/test/') }
|
97
|
+
it { should_not match('/teest/') }
|
98
|
+
end
|
99
|
+
|
100
|
+
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') }
|
104
|
+
end
|
105
|
+
|
106
|
+
pattern '/foo&bar' do
|
107
|
+
it { should match('/foo&bar') }
|
108
|
+
end
|
109
|
+
|
110
|
+
pattern '/*a/:foo/*b/*c' do
|
111
|
+
it { should match('/bar/foo/bling/baz/boom').capturing a: 'bar', foo: 'foo', b: 'bling', c: 'baz/boom' }
|
112
|
+
example { pattern.params('/bar/foo/bling/baz/boom').should be == { "a" => 'bar', "foo" => 'foo', "b" => 'bling', "c" => 'baz/boom' } }
|
113
|
+
end
|
114
|
+
|
115
|
+
pattern '/test.bar' do
|
116
|
+
it { should match('/test.bar') }
|
117
|
+
it { should_not match('/test0bar') }
|
118
|
+
end
|
119
|
+
|
120
|
+
pattern '/:file.:ext' do
|
121
|
+
it { should match('/pony.jpg') .capturing file: 'pony', ext: 'jpg' }
|
122
|
+
it { should match('/pony%2Ejpg') .capturing file: 'pony', ext: 'jpg' }
|
123
|
+
it { should match('/pony%2ejpg') .capturing file: 'pony', ext: 'jpg' }
|
124
|
+
|
125
|
+
it { should match('/pony%E6%AD%A3%2Ejpg') .capturing file: 'pony%E6%AD%A3', ext: 'jpg' }
|
126
|
+
it { should match('/pony%e6%ad%a3%2ejpg') .capturing file: 'pony%e6%ad%a3', ext: 'jpg' }
|
127
|
+
it { should match('/pony正%2Ejpg') .capturing file: 'pony正', ext: 'jpg' }
|
128
|
+
it { should match('/pony正%2ejpg') .capturing file: 'pony正', ext: 'jpg' }
|
129
|
+
it { should match('/pony正..jpg') .capturing file: 'pony正.', ext: 'jpg' }
|
130
|
+
|
131
|
+
it { should_not match('/.jpg') }
|
132
|
+
end
|
133
|
+
|
134
|
+
pattern '/:a(x)' do
|
135
|
+
it { should match('/a') .capturing a: 'a' }
|
136
|
+
it { should match('/xa') .capturing a: 'xa' }
|
137
|
+
it { should match('/axa') .capturing a: 'axa' }
|
138
|
+
it { should match('/ax') .capturing a: 'a' }
|
139
|
+
it { should match('/axax') .capturing a: 'axa' }
|
140
|
+
it { should match('/axaxx') .capturing a: 'axax' }
|
141
|
+
end
|
142
|
+
|
143
|
+
pattern '/:user(@:host)' do
|
144
|
+
it { should match('/foo@bar') .capturing user: 'foo', host: 'bar' }
|
145
|
+
it { should match('/foo.foo@bar') .capturing user: 'foo.foo', host: 'bar' }
|
146
|
+
it { should match('/foo@bar.bar') .capturing user: 'foo', host: 'bar.bar' }
|
147
|
+
end
|
148
|
+
|
149
|
+
pattern '/:file(.:ext)' do
|
150
|
+
it { should match('/pony') .capturing file: 'pony', ext: nil }
|
151
|
+
it { should match('/pony.jpg') .capturing file: 'pony', ext: 'jpg' }
|
152
|
+
it { should match('/pony%2Ejpg') .capturing file: 'pony', ext: 'jpg' }
|
153
|
+
it { should match('/pony%2ejpg') .capturing file: 'pony', ext: 'jpg' }
|
154
|
+
it { should match('/pony.png.jpg') .capturing file: 'pony.png', ext: 'jpg' }
|
155
|
+
it { should match('/pony.') .capturing file: 'pony.' }
|
156
|
+
it { should_not match('/.jpg') }
|
157
|
+
end
|
158
|
+
|
159
|
+
pattern '/:id/test.bar' do
|
160
|
+
it { should match('/3/test.bar') .capturing id: '3' }
|
161
|
+
it { should match('/2/test.bar') .capturing id: '2' }
|
162
|
+
it { should match('/2E/test.bar') .capturing id: '2E' }
|
163
|
+
it { should match('/2e/test.bar') .capturing id: '2e' }
|
164
|
+
it { should match('/%2E/test.bar') .capturing id: '%2E' }
|
165
|
+
end
|
166
|
+
|
167
|
+
pattern '/10/:id' do
|
168
|
+
it { should match('/10/test') .capturing id: 'test' }
|
169
|
+
it { should match('/10/te.st') .capturing id: 'te.st' }
|
170
|
+
end
|
171
|
+
|
172
|
+
pattern '/10.1/:id' do
|
173
|
+
it { should match('/10.1/test') .capturing id: 'test' }
|
174
|
+
it { should match('/10.1/te.st') .capturing id: 'te.st' }
|
175
|
+
end
|
176
|
+
|
177
|
+
pattern '/:foo.:bar/:id' do
|
178
|
+
it { should match('/10.1/te.st') .capturing foo: "10", bar: "1", id: "te.st" }
|
179
|
+
it { should match('/10.1.2/te.st') .capturing foo: "10.1", bar: "2", id: "te.st" }
|
180
|
+
end
|
181
|
+
|
182
|
+
pattern '/:a/:b(.)(:c)' do
|
183
|
+
it { should match('/a/b') .capturing a: 'a', b: 'b', c: nil }
|
184
|
+
it { should match('/a/b.c') .capturing a: 'a', b: 'b', c: 'c' }
|
185
|
+
it { should match('/a.b/c') .capturing a: 'a.b', b: 'c', c: nil }
|
186
|
+
it { should match('/a.b/c.d') .capturing a: 'a.b', b: 'c', c: 'd' }
|
187
|
+
it { should_not match('/a.b/c.d/e') }
|
188
|
+
end
|
189
|
+
|
190
|
+
pattern '/:a(foo:b)' do
|
191
|
+
it { should match('/barfoobar') .capturing a: 'bar', b: 'bar' }
|
192
|
+
it { should match('/barfoobarfoobar') .capturing a: 'barfoobar', b: 'bar' }
|
193
|
+
it { should match('/bar') .capturing a: 'bar', b: nil }
|
194
|
+
it { should_not match('/') }
|
195
|
+
end
|
196
|
+
|
197
|
+
pattern '/fo(o)' do
|
198
|
+
it { should match('/fo') }
|
199
|
+
it { should match('/foo') }
|
200
|
+
it { should_not match('') }
|
201
|
+
it { should_not match('/') }
|
202
|
+
it { should_not match('/f') }
|
203
|
+
it { should_not match('/fooo') }
|
204
|
+
end
|
205
|
+
|
206
|
+
pattern '/foo?' do
|
207
|
+
it { should match('/foo?') }
|
208
|
+
it { should_not match('/foo\?') }
|
209
|
+
it { should_not match('/fo') }
|
210
|
+
it { should_not match('/foo') }
|
211
|
+
it { should_not match('') }
|
212
|
+
it { should_not match('/') }
|
213
|
+
it { should_not match('/f') }
|
214
|
+
it { should_not match('/fooo') }
|
215
|
+
end
|
216
|
+
|
217
|
+
pattern '/:fOO' do
|
218
|
+
it { should match('/a').capturing fOO: 'a' }
|
219
|
+
end
|
220
|
+
|
221
|
+
pattern '/:_X' do
|
222
|
+
it { should match('/a').capturing _X: 'a' }
|
223
|
+
end
|
224
|
+
|
225
|
+
pattern '/:f00' do
|
226
|
+
it { should match('/a').capturing f00: 'a' }
|
227
|
+
end
|
228
|
+
|
229
|
+
pattern '/:foo(/:bar)/:baz' do
|
230
|
+
it { should match('/foo/bar/baz').capturing foo: 'foo', bar: 'bar', baz: 'baz' }
|
231
|
+
end
|
232
|
+
|
233
|
+
pattern '/:foo', capture: /\d+/ do
|
234
|
+
it { should match('/1') .capturing foo: '1' }
|
235
|
+
it { should match('/123') .capturing foo: '123' }
|
236
|
+
|
237
|
+
it { should_not match('/') }
|
238
|
+
it { should_not match('/foo') }
|
239
|
+
end
|
240
|
+
|
241
|
+
pattern '/:foo', capture: /\d+/ do
|
242
|
+
it { should match('/1') .capturing foo: '1' }
|
243
|
+
it { should match('/123') .capturing foo: '123' }
|
244
|
+
|
245
|
+
it { should_not match('/') }
|
246
|
+
it { should_not match('/foo') }
|
247
|
+
end
|
248
|
+
|
249
|
+
pattern '/:foo', capture: '1' do
|
250
|
+
it { should match('/1').capturing foo: '1' }
|
251
|
+
|
252
|
+
it { should_not match('/') }
|
253
|
+
it { should_not match('/foo') }
|
254
|
+
it { should_not match('/123') }
|
255
|
+
end
|
256
|
+
|
257
|
+
pattern '/:foo', capture: 'a.b' do
|
258
|
+
it { should match('/a.b') .capturing foo: 'a.b' }
|
259
|
+
it { should match('/a%2Eb') .capturing foo: 'a%2Eb' }
|
260
|
+
it { should match('/a%2eb') .capturing foo: 'a%2eb' }
|
261
|
+
|
262
|
+
it { should_not match('/ab') }
|
263
|
+
it { should_not match('/afb') }
|
264
|
+
it { should_not match('/a1b') }
|
265
|
+
it { should_not match('/a.bc') }
|
266
|
+
end
|
267
|
+
|
268
|
+
pattern '/:foo(/:bar)', capture: :alpha do
|
269
|
+
it { should match('/abc') .capturing foo: 'abc', bar: nil }
|
270
|
+
it { should match('/a/b') .capturing foo: 'a', bar: 'b' }
|
271
|
+
it { should match('/a') .capturing foo: 'a', bar: nil }
|
272
|
+
|
273
|
+
it { should_not match('/1/2') }
|
274
|
+
it { should_not match('/a/2') }
|
275
|
+
it { should_not match('/1/b') }
|
276
|
+
it { should_not match('/1') }
|
277
|
+
it { should_not match('/1/') }
|
278
|
+
it { should_not match('/a/') }
|
279
|
+
it { should_not match('//a') }
|
280
|
+
end
|
281
|
+
|
282
|
+
pattern '/:foo', capture: ['foo', 'bar', /\d+/] do
|
283
|
+
it { should match('/1') .capturing foo: '1' }
|
284
|
+
it { should match('/123') .capturing foo: '123' }
|
285
|
+
it { should match('/foo') .capturing foo: 'foo' }
|
286
|
+
it { should match('/bar') .capturing foo: 'bar' }
|
287
|
+
|
288
|
+
it { should_not match('/') }
|
289
|
+
it { should_not match('/baz') }
|
290
|
+
it { should_not match('/foo1') }
|
291
|
+
end
|
292
|
+
|
293
|
+
pattern '/:foo:bar:baz', capture: { foo: :alpha, bar: /\d+/ } do
|
294
|
+
it { should match('/ab123xy-1') .capturing foo: 'ab', bar: '123', baz: 'xy-1' }
|
295
|
+
it { should match('/ab123') .capturing foo: 'ab', bar: '12', baz: '3' }
|
296
|
+
it { should_not match('/123abcxy-1') }
|
297
|
+
it { should_not match('/abcxy-1') }
|
298
|
+
it { should_not match('/abc1') }
|
299
|
+
end
|
300
|
+
|
301
|
+
pattern '/:foo', capture: { foo: ['foo', 'bar', /\d+/] } do
|
302
|
+
it { should match('/1') .capturing foo: '1' }
|
303
|
+
it { should match('/123') .capturing foo: '123' }
|
304
|
+
it { should match('/foo') .capturing foo: 'foo' }
|
305
|
+
it { should match('/bar') .capturing foo: 'bar' }
|
306
|
+
|
307
|
+
it { should_not match('/') }
|
308
|
+
it { should_not match('/baz') }
|
309
|
+
it { should_not match('/foo1') }
|
310
|
+
end
|
311
|
+
|
312
|
+
pattern '/:file(.:ext)', capture: { ext: ['jpg', 'png'] } do
|
313
|
+
it { should match('/pony') .capturing file: 'pony', ext: nil }
|
314
|
+
it { should match('/pony.jpg') .capturing file: 'pony', ext: 'jpg' }
|
315
|
+
it { should match('/pony%2Ejpg') .capturing file: 'pony', ext: 'jpg' }
|
316
|
+
it { should match('/pony%2ejpg') .capturing file: 'pony', ext: 'jpg' }
|
317
|
+
it { should match('/pony.png') .capturing file: 'pony', ext: 'png' }
|
318
|
+
it { should match('/pony%2Epng') .capturing file: 'pony', ext: 'png' }
|
319
|
+
it { should match('/pony%2epng') .capturing file: 'pony', ext: 'png' }
|
320
|
+
it { should match('/pony.png.jpg') .capturing file: 'pony.png', ext: 'jpg' }
|
321
|
+
it { should match('/pony.jpg.png') .capturing file: 'pony.jpg', ext: 'png' }
|
322
|
+
it { should match('/pony.gif') .capturing file: 'pony.gif', ext: nil }
|
323
|
+
it { should match('/pony.') .capturing file: 'pony.', ext: nil }
|
324
|
+
it { should_not match('.jpg') }
|
325
|
+
end
|
326
|
+
|
327
|
+
pattern '/:file(:ext)', capture: { ext: ['.jpg', '.png', '.tar.gz'] } do
|
328
|
+
it { should match('/pony') .capturing file: 'pony', ext: nil }
|
329
|
+
it { should match('/pony.jpg') .capturing file: 'pony', ext: '.jpg' }
|
330
|
+
it { should match('/pony.png') .capturing file: 'pony', ext: '.png' }
|
331
|
+
it { should match('/pony.png.jpg') .capturing file: 'pony.png', ext: '.jpg' }
|
332
|
+
it { should match('/pony.jpg.png') .capturing file: 'pony.jpg', ext: '.png' }
|
333
|
+
it { should match('/pony.tar.gz') .capturing file: 'pony', ext: '.tar.gz' }
|
334
|
+
it { should match('/pony.gif') .capturing file: 'pony.gif', ext: nil }
|
335
|
+
it { should match('/pony.') .capturing file: 'pony.', ext: nil }
|
336
|
+
it { should_not match('/.jpg') }
|
337
|
+
end
|
338
|
+
|
339
|
+
pattern '/:a(@:b)', capture: { b: /\d+/ } do
|
340
|
+
it { should match('/a') .capturing a: 'a', b: nil }
|
341
|
+
it { should match('/a@1') .capturing a: 'a', b: '1' }
|
342
|
+
it { should match('/a@b') .capturing a: 'a@b', b: nil }
|
343
|
+
it { should match('/a@1@2') .capturing a: 'a@1', b: '2' }
|
344
|
+
end
|
345
|
+
|
346
|
+
pattern '/:a(b)', greedy: false do
|
347
|
+
it { should match('/ab').capturing a: 'a' }
|
348
|
+
end
|
349
|
+
|
350
|
+
pattern '/:file(.:ext)', greedy: false do
|
351
|
+
it { should match('/pony') .capturing file: 'pony', ext: nil }
|
352
|
+
it { should match('/pony.jpg') .capturing file: 'pony', ext: 'jpg' }
|
353
|
+
it { should match('/pony.png.jpg') .capturing file: 'pony', ext: 'png.jpg' }
|
354
|
+
end
|
355
|
+
|
356
|
+
pattern '/:controller(/:action(/:id(.:format)))' do
|
357
|
+
it { should match('/content').capturing controller: 'content' }
|
358
|
+
end
|
359
|
+
|
360
|
+
pattern '/fo(o)', uri_decode: false do
|
361
|
+
it { should match('/foo') }
|
362
|
+
it { should match('/fo') }
|
363
|
+
it { should_not match('/fo(o)') }
|
364
|
+
end
|
365
|
+
|
366
|
+
pattern '/foo/bar', uri_decode: false do
|
367
|
+
it { should match('/foo/bar') }
|
368
|
+
it { should_not match('/foo%2Fbar') }
|
369
|
+
it { should_not match('/foo%2fbar') }
|
370
|
+
end
|
371
|
+
|
372
|
+
pattern "/path with spaces", uri_decode: false do
|
373
|
+
it { should match('/path with spaces') }
|
374
|
+
it { should_not match('/path%20with%20spaces') }
|
375
|
+
it { should_not match('/path%2Bwith%2Bspaces') }
|
376
|
+
it { should_not match('/path+with+spaces') }
|
377
|
+
end
|
378
|
+
|
379
|
+
pattern "/path with spaces", space_matches_plus: false do
|
380
|
+
it { should match('/path%20with%20spaces') }
|
381
|
+
it { should_not match('/path%2Bwith%2Bspaces') }
|
382
|
+
it { should_not match('/path+with+spaces') }
|
383
|
+
end
|
384
|
+
|
385
|
+
context 'invalid syntax' do
|
386
|
+
example 'unexpected closing parenthesis' do
|
387
|
+
expect { Mustermann::Rails.new('foo)bar') }.
|
388
|
+
to raise_error(Mustermann::ParseError, 'unexpected ) while parsing "foo)bar"')
|
389
|
+
end
|
390
|
+
|
391
|
+
example 'missing closing parenthesis' do
|
392
|
+
expect { Mustermann::Rails.new('foo(bar') }.
|
393
|
+
to raise_error(Mustermann::ParseError, 'unexpected end of string while parsing "foo(bar"')
|
394
|
+
end
|
395
|
+
end
|
396
|
+
|
397
|
+
context 'invalid capture names' do
|
398
|
+
example 'empty name' do
|
399
|
+
expect { Mustermann::Rails.new('/:/') }.
|
400
|
+
to raise_error(Mustermann::CompileError, "capture name can't be empty: \"/:/\"")
|
401
|
+
end
|
402
|
+
|
403
|
+
example 'named splat' do
|
404
|
+
expect { Mustermann::Rails.new('/:splat/') }.
|
405
|
+
to raise_error(Mustermann::CompileError, "capture name can't be splat: \"/:splat/\"")
|
406
|
+
end
|
407
|
+
|
408
|
+
example 'named captures' do
|
409
|
+
expect { Mustermann::Rails.new('/:captures/') }.
|
410
|
+
to raise_error(Mustermann::CompileError, "capture name can't be captures: \"/:captures/\"")
|
411
|
+
end
|
412
|
+
|
413
|
+
example 'with capital letter' do
|
414
|
+
expect { Mustermann::Rails.new('/:Foo/') }.
|
415
|
+
to raise_error(Mustermann::CompileError, "capture name must start with underscore or lower case letter: \"/:Foo/\"")
|
416
|
+
end
|
417
|
+
|
418
|
+
example 'with integer' do
|
419
|
+
expect { Mustermann::Rails.new('/:1a/') }.
|
420
|
+
to raise_error(Mustermann::CompileError, "capture name must start with underscore or lower case letter: \"/:1a/\"")
|
421
|
+
end
|
422
|
+
|
423
|
+
example 'same name twice' do
|
424
|
+
expect { Mustermann::Rails.new('/:foo(/:bar)/:bar') }.
|
425
|
+
to raise_error(Mustermann::CompileError, "can't use the same capture name twice: \"/:foo(/:bar)/:bar\"")
|
426
|
+
end
|
427
|
+
end
|
428
|
+
|
429
|
+
context 'Regexp compatibility' do
|
430
|
+
describe :=== do
|
431
|
+
example('non-matching') { Mustermann::Rails.new("/") .should_not be === '/foo' }
|
432
|
+
example('matching') { Mustermann::Rails.new("/:foo") .should be === '/foo' }
|
433
|
+
end
|
434
|
+
|
435
|
+
describe :=~ do
|
436
|
+
example('non-matching') { Mustermann::Rails.new("/") .should_not be =~ '/foo' }
|
437
|
+
example('matching') { Mustermann::Rails.new("/:foo") .should be =~ '/foo' }
|
438
|
+
|
439
|
+
context 'String#=~' do
|
440
|
+
example('non-matching') { "/foo".should_not be =~ Mustermann::Rails.new("/") }
|
441
|
+
example('matching') { "/foo".should be =~ Mustermann::Rails.new("/:foo") }
|
442
|
+
end
|
443
|
+
end
|
444
|
+
|
445
|
+
describe :to_regexp do
|
446
|
+
example('empty pattern') { Mustermann::Rails.new('').to_regexp.should be == /\A\Z/ }
|
447
|
+
|
448
|
+
context 'Regexp.try_convert' do
|
449
|
+
example('empty pattern') { Regexp.try_convert(Mustermann::Rails.new('')).should be == /\A\Z/ }
|
450
|
+
end
|
451
|
+
end
|
452
|
+
end
|
453
|
+
end
|