sinatra-acd 1.4.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (128) hide show
  1. checksums.yaml +7 -0
  2. data/.yardopts +5 -0
  3. data/AUTHORS +61 -0
  4. data/CHANGES +1293 -0
  5. data/Gemfile +76 -0
  6. data/LICENSE +23 -0
  7. data/README.de.md +2864 -0
  8. data/README.es.md +2786 -0
  9. data/README.fr.md +2924 -0
  10. data/README.hu.md +694 -0
  11. data/README.ja.md +2726 -0
  12. data/README.ko.md +2832 -0
  13. data/README.md +2980 -0
  14. data/README.pt-br.md +965 -0
  15. data/README.pt-pt.md +791 -0
  16. data/README.ru.md +2799 -0
  17. data/README.zh.md +2158 -0
  18. data/Rakefile +199 -0
  19. data/examples/chat.rb +61 -0
  20. data/examples/simple.rb +3 -0
  21. data/examples/stream.ru +26 -0
  22. data/lib/sinatra.rb +4 -0
  23. data/lib/sinatra/base.rb +2044 -0
  24. data/lib/sinatra/images/404.png +0 -0
  25. data/lib/sinatra/images/500.png +0 -0
  26. data/lib/sinatra/main.rb +34 -0
  27. data/lib/sinatra/show_exceptions.rb +345 -0
  28. data/lib/sinatra/version.rb +3 -0
  29. data/sinatra.gemspec +19 -0
  30. data/test/asciidoctor_test.rb +72 -0
  31. data/test/base_test.rb +171 -0
  32. data/test/builder_test.rb +91 -0
  33. data/test/coffee_test.rb +90 -0
  34. data/test/compile_test.rb +183 -0
  35. data/test/contest.rb +100 -0
  36. data/test/creole_test.rb +65 -0
  37. data/test/delegator_test.rb +160 -0
  38. data/test/encoding_test.rb +20 -0
  39. data/test/erb_test.rb +116 -0
  40. data/test/extensions_test.rb +98 -0
  41. data/test/filter_test.rb +487 -0
  42. data/test/haml_test.rb +109 -0
  43. data/test/helper.rb +131 -0
  44. data/test/helpers_test.rb +1917 -0
  45. data/test/integration/app.rb +79 -0
  46. data/test/integration_helper.rb +236 -0
  47. data/test/integration_test.rb +104 -0
  48. data/test/less_test.rb +69 -0
  49. data/test/liquid_test.rb +77 -0
  50. data/test/mapped_error_test.rb +285 -0
  51. data/test/markaby_test.rb +80 -0
  52. data/test/markdown_test.rb +82 -0
  53. data/test/mediawiki_test.rb +68 -0
  54. data/test/middleware_test.rb +68 -0
  55. data/test/nokogiri_test.rb +67 -0
  56. data/test/public/favicon.ico +0 -0
  57. data/test/rabl_test.rb +89 -0
  58. data/test/rack_test.rb +45 -0
  59. data/test/radius_test.rb +59 -0
  60. data/test/rdoc_test.rb +66 -0
  61. data/test/readme_test.rb +130 -0
  62. data/test/request_test.rb +97 -0
  63. data/test/response_test.rb +63 -0
  64. data/test/result_test.rb +76 -0
  65. data/test/route_added_hook_test.rb +59 -0
  66. data/test/routing_test.rb +1412 -0
  67. data/test/sass_test.rb +115 -0
  68. data/test/scss_test.rb +88 -0
  69. data/test/server_test.rb +48 -0
  70. data/test/settings_test.rb +582 -0
  71. data/test/sinatra_test.rb +12 -0
  72. data/test/slim_test.rb +102 -0
  73. data/test/static_test.rb +236 -0
  74. data/test/streaming_test.rb +149 -0
  75. data/test/stylus_test.rb +90 -0
  76. data/test/templates_test.rb +382 -0
  77. data/test/textile_test.rb +65 -0
  78. data/test/views/a/in_a.str +1 -0
  79. data/test/views/ascii.erb +2 -0
  80. data/test/views/b/in_b.str +1 -0
  81. data/test/views/calc.html.erb +1 -0
  82. data/test/views/error.builder +3 -0
  83. data/test/views/error.erb +3 -0
  84. data/test/views/error.haml +3 -0
  85. data/test/views/error.sass +2 -0
  86. data/test/views/explicitly_nested.str +1 -0
  87. data/test/views/foo/hello.test +1 -0
  88. data/test/views/hello.asciidoc +1 -0
  89. data/test/views/hello.builder +1 -0
  90. data/test/views/hello.coffee +1 -0
  91. data/test/views/hello.creole +1 -0
  92. data/test/views/hello.erb +1 -0
  93. data/test/views/hello.haml +1 -0
  94. data/test/views/hello.less +5 -0
  95. data/test/views/hello.liquid +1 -0
  96. data/test/views/hello.mab +1 -0
  97. data/test/views/hello.md +1 -0
  98. data/test/views/hello.mediawiki +1 -0
  99. data/test/views/hello.nokogiri +1 -0
  100. data/test/views/hello.rabl +2 -0
  101. data/test/views/hello.radius +1 -0
  102. data/test/views/hello.rdoc +1 -0
  103. data/test/views/hello.sass +2 -0
  104. data/test/views/hello.scss +3 -0
  105. data/test/views/hello.slim +1 -0
  106. data/test/views/hello.str +1 -0
  107. data/test/views/hello.styl +2 -0
  108. data/test/views/hello.test +1 -0
  109. data/test/views/hello.textile +1 -0
  110. data/test/views/hello.wlang +1 -0
  111. data/test/views/hello.yajl +1 -0
  112. data/test/views/layout2.builder +3 -0
  113. data/test/views/layout2.erb +2 -0
  114. data/test/views/layout2.haml +2 -0
  115. data/test/views/layout2.liquid +2 -0
  116. data/test/views/layout2.mab +2 -0
  117. data/test/views/layout2.nokogiri +3 -0
  118. data/test/views/layout2.rabl +3 -0
  119. data/test/views/layout2.radius +2 -0
  120. data/test/views/layout2.slim +3 -0
  121. data/test/views/layout2.str +2 -0
  122. data/test/views/layout2.test +1 -0
  123. data/test/views/layout2.wlang +2 -0
  124. data/test/views/nested.str +1 -0
  125. data/test/views/utf8.erb +2 -0
  126. data/test/wlang_test.rb +87 -0
  127. data/test/yajl_test.rb +86 -0
  128. metadata +280 -0
@@ -0,0 +1,171 @@
1
+ require File.expand_path('../helper', __FILE__)
2
+
3
+ class BaseTest < Test::Unit::TestCase
4
+ def test_default
5
+ assert true
6
+ end
7
+
8
+ describe 'Sinatra::Base subclasses' do
9
+ class TestApp < Sinatra::Base
10
+ get('/') { 'Hello World' }
11
+ end
12
+
13
+ it 'include Rack::Utils' do
14
+ assert TestApp.included_modules.include?(Rack::Utils)
15
+ end
16
+
17
+ it 'processes requests with #call' do
18
+ assert TestApp.respond_to?(:call)
19
+
20
+ request = Rack::MockRequest.new(TestApp)
21
+ response = request.get('/')
22
+ assert response.ok?
23
+ assert_equal 'Hello World', response.body
24
+ end
25
+
26
+ class TestApp < Sinatra::Base
27
+ get '/state' do
28
+ @foo ||= "new"
29
+ body = "Foo: #{@foo}"
30
+ @foo = 'discard'
31
+ body
32
+ end
33
+ end
34
+
35
+ it 'does not maintain state between requests' do
36
+ request = Rack::MockRequest.new(TestApp)
37
+ 2.times do
38
+ response = request.get('/state')
39
+ assert response.ok?
40
+ assert_equal 'Foo: new', response.body
41
+ end
42
+ end
43
+
44
+ it "passes the subclass to configure blocks" do
45
+ ref = nil
46
+ TestApp.configure { |app| ref = app }
47
+ assert_equal TestApp, ref
48
+ end
49
+
50
+ it "allows the configure block arg to be omitted and does not change context" do
51
+ context = nil
52
+ TestApp.configure { context = self }
53
+ assert_equal self, context
54
+ end
55
+ end
56
+
57
+ describe "Sinatra::Base#new" do
58
+ it 'returns a wrapper' do
59
+ assert_equal Sinatra::Wrapper, Sinatra::Base.new.class
60
+ end
61
+
62
+ it 'implements a nice inspect' do
63
+ assert_equal '#<Sinatra::Base app_file=nil>', Sinatra::Base.new.inspect
64
+ end
65
+
66
+ it 'exposes settings' do
67
+ assert_equal Sinatra::Base.settings, Sinatra::Base.new.settings
68
+ end
69
+
70
+ it 'exposes helpers' do
71
+ assert_equal 'image/jpeg', Sinatra::Base.new.helpers.mime_type(:jpg)
72
+ end
73
+ end
74
+
75
+ describe "Sinatra::Base as Rack middleware" do
76
+ app = lambda { |env|
77
+ headers = {'X-Downstream' => 'true'}
78
+ headers['X-Route-Missing'] = env['sinatra.route-missing'] || ''
79
+ [210, headers, ['Hello from downstream']] }
80
+
81
+ class TestMiddleware < Sinatra::Base
82
+ end
83
+
84
+ it 'creates a middleware that responds to #call with .new' do
85
+ middleware = TestMiddleware.new(app)
86
+ assert middleware.respond_to?(:call)
87
+ end
88
+
89
+ it 'exposes the downstream app' do
90
+ middleware = TestMiddleware.new!(app)
91
+ assert_same app, middleware.app
92
+ end
93
+
94
+ class TestMiddleware < Sinatra::Base
95
+ def route_missing
96
+ env['sinatra.route-missing'] = '1'
97
+ super
98
+ end
99
+
100
+ get('/') { 'Hello from middleware' }
101
+ end
102
+
103
+ middleware = TestMiddleware.new(app)
104
+ request = Rack::MockRequest.new(middleware)
105
+
106
+ it 'intercepts requests' do
107
+ response = request.get('/')
108
+ assert response.ok?
109
+ assert_equal 'Hello from middleware', response.body
110
+ end
111
+
112
+ it 'automatically forwards requests downstream when no matching route found' do
113
+ response = request.get('/missing')
114
+ assert_equal 210, response.status
115
+ assert_equal 'Hello from downstream', response.body
116
+ end
117
+
118
+ it 'calls #route_missing before forwarding downstream' do
119
+ response = request.get('/missing')
120
+ assert_equal '1', response['X-Route-Missing']
121
+ end
122
+
123
+ class TestMiddleware < Sinatra::Base
124
+ get('/low-level-forward') { app.call(env) }
125
+ end
126
+
127
+ it 'can call the downstream app directly and return result' do
128
+ response = request.get('/low-level-forward')
129
+ assert_equal 210, response.status
130
+ assert_equal 'true', response['X-Downstream']
131
+ assert_equal 'Hello from downstream', response.body
132
+ end
133
+
134
+ class TestMiddleware < Sinatra::Base
135
+ get '/explicit-forward' do
136
+ response['X-Middleware'] = 'true'
137
+ res = forward
138
+ assert_nil res
139
+ assert_equal 210, response.status
140
+ assert_equal 'true', response['X-Downstream']
141
+ assert_equal ['Hello from downstream'], response.body
142
+ 'Hello after explicit forward'
143
+ end
144
+ end
145
+
146
+ it 'forwards the request downstream and integrates the response into the current context' do
147
+ response = request.get('/explicit-forward')
148
+ assert_equal 210, response.status
149
+ assert_equal 'true', response['X-Downstream']
150
+ assert_equal 'Hello after explicit forward', response.body
151
+ assert_equal '28', response['Content-Length']
152
+ end
153
+
154
+ app_content_length = lambda {|env|
155
+ [200, {'Content-Length' => '16'}, 'From downstream!']}
156
+
157
+ class TestMiddlewareContentLength < Sinatra::Base
158
+ get '/forward' do
159
+ 'From after explicit forward!'
160
+ end
161
+ end
162
+
163
+ middleware_content_length = TestMiddlewareContentLength.new(app_content_length)
164
+ request_content_length = Rack::MockRequest.new(middleware_content_length)
165
+
166
+ it "sets content length for last response" do
167
+ response = request_content_length.get('/forward')
168
+ assert_equal '28', response['Content-Length']
169
+ end
170
+ end
171
+ end
@@ -0,0 +1,91 @@
1
+ require File.expand_path('../helper', __FILE__)
2
+
3
+ begin
4
+ require 'builder'
5
+
6
+ class BuilderTest < Test::Unit::TestCase
7
+ def builder_app(options = {}, &block)
8
+ mock_app do
9
+ set :views, File.dirname(__FILE__) + '/views'
10
+ set options
11
+ get('/', &block)
12
+ end
13
+ get '/'
14
+ end
15
+
16
+ it 'renders inline Builder strings' do
17
+ builder_app { builder 'xml.instruct!' }
18
+ assert ok?
19
+ assert_equal %{<?xml version="1.0" encoding="UTF-8"?>\n}, body
20
+ end
21
+
22
+ it 'defaults content type to xml' do
23
+ builder_app { builder 'xml.instruct!' }
24
+ assert ok?
25
+ assert_equal "application/xml;charset=utf-8", response['Content-Type']
26
+ end
27
+
28
+ it 'defaults allows setting content type per route' do
29
+ builder_app do
30
+ content_type :html
31
+ builder 'xml.instruct!'
32
+ end
33
+ assert ok?
34
+ assert_equal "text/html;charset=utf-8", response['Content-Type']
35
+ end
36
+
37
+ it 'defaults allows setting content type globally' do
38
+ builder_app(:builder => { :content_type => 'html' }) do
39
+ builder 'xml.instruct!'
40
+ end
41
+ assert ok?
42
+ assert_equal "text/html;charset=utf-8", response['Content-Type']
43
+ end
44
+
45
+ it 'renders inline blocks' do
46
+ builder_app do
47
+ @name = "Frank & Mary"
48
+ builder { |xml| xml.couple @name }
49
+ end
50
+ assert ok?
51
+ assert_equal "<couple>Frank &amp; Mary</couple>\n", body
52
+ end
53
+
54
+ it 'renders .builder files in views path' do
55
+ builder_app do
56
+ @name = "Blue"
57
+ builder :hello
58
+ end
59
+ assert ok?
60
+ assert_equal %(<exclaim>You're my boy, Blue!</exclaim>\n), body
61
+ end
62
+
63
+ it "renders with inline layouts" do
64
+ mock_app do
65
+ layout { %(xml.layout { xml << yield }) }
66
+ get('/') { builder %(xml.em 'Hello World') }
67
+ end
68
+ get '/'
69
+ assert ok?
70
+ assert_equal "<layout>\n<em>Hello World</em>\n</layout>\n", body
71
+ end
72
+
73
+ it "renders with file layouts" do
74
+ builder_app do
75
+ builder %(xml.em 'Hello World'), :layout => :layout2
76
+ end
77
+ assert ok?
78
+ assert_equal "<layout>\n<em>Hello World</em>\n</layout>\n", body
79
+ end
80
+
81
+ it "raises error if template not found" do
82
+ mock_app do
83
+ get('/') { builder :no_such_template }
84
+ end
85
+ assert_raise(Errno::ENOENT) { get('/') }
86
+ end
87
+ end
88
+
89
+ rescue LoadError
90
+ warn "#{$!.to_s}: skipping builder tests"
91
+ end
@@ -0,0 +1,90 @@
1
+ require File.expand_path('../helper', __FILE__)
2
+
3
+ begin
4
+ require 'coffee-script'
5
+ require 'execjs'
6
+
7
+ begin
8
+ ExecJS.compile '1'
9
+ rescue Exception
10
+ raise LoadError, 'unable to execute JavaScript'
11
+ end
12
+
13
+ class CoffeeTest < Test::Unit::TestCase
14
+ def coffee_app(options = {}, &block)
15
+ mock_app do
16
+ set :views, File.dirname(__FILE__) + '/views'
17
+ set(options)
18
+ get('/', &block)
19
+ end
20
+ get '/'
21
+ end
22
+
23
+ it 'renders inline Coffee strings' do
24
+ coffee_app { coffee "alert 'Aye!'\n" }
25
+ assert ok?
26
+ assert body.include?("alert('Aye!');")
27
+ end
28
+
29
+ it 'defaults content type to javascript' do
30
+ coffee_app { coffee "alert 'Aye!'\n" }
31
+ assert ok?
32
+ assert_equal "application/javascript;charset=utf-8", response['Content-Type']
33
+ end
34
+
35
+ it 'defaults allows setting content type per route' do
36
+ coffee_app do
37
+ content_type :html
38
+ coffee "alert 'Aye!'\n"
39
+ end
40
+ assert ok?
41
+ assert_equal "text/html;charset=utf-8", response['Content-Type']
42
+ end
43
+
44
+ it 'defaults allows setting content type globally' do
45
+ coffee_app(:coffee => { :content_type => 'html' }) do
46
+ coffee "alert 'Aye!'\n"
47
+ end
48
+ assert ok?
49
+ assert_equal "text/html;charset=utf-8", response['Content-Type']
50
+ end
51
+
52
+ it 'renders .coffee files in views path' do
53
+ coffee_app { coffee :hello }
54
+ assert ok?
55
+ assert_include body, "alert(\"Aye!\");"
56
+ end
57
+
58
+ it 'ignores the layout option' do
59
+ coffee_app { coffee :hello, :layout => :layout2 }
60
+ assert ok?
61
+ assert_include body, "alert(\"Aye!\");"
62
+ end
63
+
64
+ it "raises error if template not found" do
65
+ mock_app {
66
+ get('/') { coffee :no_such_template }
67
+ }
68
+ assert_raise(Errno::ENOENT) { get('/') }
69
+ end
70
+
71
+ it "passes coffee options to the coffee engine" do
72
+ coffee_app { coffee "alert 'Aye!'\n", :no_wrap => true }
73
+ assert ok?
74
+ assert_body "alert('Aye!');"
75
+ end
76
+
77
+ it "passes default coffee options to the coffee engine" do
78
+ mock_app do
79
+ set :coffee, :no_wrap => true # default coffee style is :nested
80
+ get('/') { coffee "alert 'Aye!'\n" }
81
+ end
82
+ get '/'
83
+ assert ok?
84
+ assert_body "alert('Aye!');"
85
+ end
86
+ end
87
+
88
+ rescue LoadError
89
+ warn "#{$!.to_s}: skipping coffee tests"
90
+ end
@@ -0,0 +1,183 @@
1
+ # I like coding: UTF-8
2
+ require File.expand_path('../helper', __FILE__)
3
+
4
+ class CompileTest < Test::Unit::TestCase
5
+
6
+ def self.converts pattern, expected_regexp
7
+ it "generates #{expected_regexp.source} from #{pattern}" do
8
+ compiled, _ = compiled pattern
9
+ assert_equal expected_regexp, compiled, "Pattern #{pattern} is not compiled into #{expected_regexp.source}. Was #{compiled.source}."
10
+ end
11
+ end
12
+ def self.parses pattern, example, expected_params
13
+ it "parses #{example} with #{pattern} into params #{expected_params}" do
14
+ compiled, keys = compiled pattern
15
+ match = compiled.match(example)
16
+ fail %Q{"#{example}" does not parse on pattern "#{pattern}" (compiled pattern is #{compiled.source}).} unless match
17
+
18
+ # Aggregate e.g. multiple splat values into one array.
19
+ #
20
+ params = keys.zip(match.captures).reduce({}) do |hash, mapping|
21
+ key, value = mapping
22
+ hash[key] = if existing = hash[key]
23
+ existing.respond_to?(:to_ary) ? existing << value : [existing, value]
24
+ else
25
+ value
26
+ end
27
+ hash
28
+ end
29
+
30
+ assert_equal expected_params, params, "Pattern #{pattern} does not match path #{example}."
31
+ end
32
+ end
33
+ def self.fails pattern, example
34
+ it "does not parse #{example} with #{pattern}" do
35
+ compiled, _ = compiled pattern
36
+ match = compiled.match(example)
37
+ fail %Q{"#{pattern}" does parse "#{example}" but it should fail} if match
38
+ end
39
+ end
40
+ def compiled pattern
41
+ app ||= mock_app {}
42
+ compiled, keys = app.send(:compile, pattern)
43
+ [compiled, keys]
44
+ end
45
+
46
+ converts "/", %r{\A/\z}
47
+ parses "/", "/", {}
48
+
49
+ converts "/foo", %r{\A/foo\z}
50
+ parses "/foo", "/foo", {}
51
+
52
+ converts "/:foo", %r{\A/([^/?#]+)\z}
53
+ parses "/:foo", "/foo", "foo" => "foo"
54
+ parses "/:foo", "/foo.bar", "foo" => "foo.bar"
55
+ parses "/:foo", "/foo%2Fbar", "foo" => "foo%2Fbar"
56
+ parses "/:foo", "/%0Afoo", "foo" => "%0Afoo"
57
+ fails "/:foo", "/foo?"
58
+ fails "/:foo", "/foo/bar"
59
+ fails "/:foo", "/"
60
+ fails "/:foo", "/foo/"
61
+
62
+ converts "/föö", %r{\A/f%[Cc]3%[Bb]6%[Cc]3%[Bb]6\z}
63
+ parses "/föö", "/f%C3%B6%C3%B6", {}
64
+
65
+ converts "/:foo/:bar", %r{\A/([^/?#]+)/([^/?#]+)\z}
66
+ parses "/:foo/:bar", "/foo/bar", "foo" => "foo", "bar" => "bar"
67
+
68
+ converts "/hello/:person", %r{\A/hello/([^/?#]+)\z}
69
+ parses "/hello/:person", "/hello/Frank", "person" => "Frank"
70
+
71
+ converts "/?:foo?/?:bar?", %r{\A/?([^/?#]+)?/?([^/?#]+)?\z}
72
+ parses "/?:foo?/?:bar?", "/hello/world", "foo" => "hello", "bar" => "world"
73
+ parses "/?:foo?/?:bar?", "/hello", "foo" => "hello", "bar" => nil
74
+ parses "/?:foo?/?:bar?", "/", "foo" => nil, "bar" => nil
75
+ parses "/?:foo?/?:bar?", "", "foo" => nil, "bar" => nil
76
+
77
+ converts "/*", %r{\A/(.*?)\z}
78
+ parses "/*", "/", "splat" => ""
79
+ parses "/*", "/foo", "splat" => "foo"
80
+ parses "/*", "/foo/bar", "splat" => "foo/bar"
81
+
82
+ converts "/:foo/*", %r{\A/([^/?#]+)/(.*?)\z}
83
+ parses "/:foo/*", "/foo/bar/baz", "foo" => "foo", "splat" => "bar/baz"
84
+
85
+ converts "/:foo/:bar", %r{\A/([^/?#]+)/([^/?#]+)\z}
86
+ parses "/:foo/:bar", "/user@example.com/name", "foo" => "user@example.com", "bar" => "name"
87
+
88
+ converts "/test$/", %r{\A/test(?:\$|%24)/\z}
89
+ parses "/test$/", "/test$/", {}
90
+
91
+ converts "/te+st/", %r{\A/te(?:\+|%2[Bb])st/\z}
92
+ parses "/te+st/", "/te+st/", {}
93
+ fails "/te+st/", "/test/"
94
+ fails "/te+st/", "/teeest/"
95
+
96
+ converts "/test(bar)/", %r{\A/test(?:\(|%28)bar(?:\)|%29)/\z}
97
+ parses "/test(bar)/", "/test(bar)/", {}
98
+
99
+ converts "/path with spaces", %r{\A/path(?:%20|(?:\+|%2[Bb]))with(?:%20|(?:\+|%2[Bb]))spaces\z}
100
+ parses "/path with spaces", "/path%20with%20spaces", {}
101
+ parses "/path with spaces", "/path%2Bwith%2Bspaces", {}
102
+ parses "/path with spaces", "/path+with+spaces", {}
103
+
104
+ converts "/foo&bar", %r{\A/foo(?:&|%26)bar\z}
105
+ parses "/foo&bar", "/foo&bar", {}
106
+
107
+ converts "/:foo/*", %r{\A/([^/?#]+)/(.*?)\z}
108
+ parses "/:foo/*", "/hello%20world/how%20are%20you", "foo" => "hello%20world", "splat" => "how%20are%20you"
109
+
110
+ converts "/*/foo/*/*", %r{\A/(.*?)/foo/(.*?)/(.*?)\z}
111
+ parses "/*/foo/*/*", "/bar/foo/bling/baz/boom", "splat" => ["bar", "bling", "baz/boom"]
112
+ fails "/*/foo/*/*", "/bar/foo/baz"
113
+
114
+ converts "/test.bar", %r{\A/test(?:\.|%2[Ee])bar\z}
115
+ parses "/test.bar", "/test.bar", {}
116
+ fails "/test.bar", "/test0bar"
117
+
118
+ converts "/:file.:ext", %r{\A/((?:[^\./?#%]|(?:%[^2].|%[2][^Ee]))+)(?:\.|%2[Ee])((?:[^/?#%]|(?:%[^2].|%[2][^Ee]))+)\z}
119
+ parses "/:file.:ext", "/pony.jpg", "file" => "pony", "ext" => "jpg"
120
+ parses "/:file.:ext", "/pony%2Ejpg", "file" => "pony", "ext" => "jpg"
121
+ fails "/:file.:ext", "/.jpg"
122
+
123
+ converts "/:name.?:format?", %r{\A/((?:[^\./?#%]|(?:%[^2].|%[2][^Ee]))+)(?:\.|%2[Ee])?((?:[^/?#%]|(?:%[^2].|%[2][^Ee]))+)?\z}
124
+ parses "/:name.?:format?", "/foo", "name" => "foo", "format" => nil
125
+ parses "/:name.?:format?", "/foo.bar", "name" => "foo", "format" => "bar"
126
+ parses "/:name.?:format?", "/foo%2Ebar", "name" => "foo", "format" => "bar"
127
+ parses "/:name?.?:format", "/.bar", "name" => nil, "format" => "bar"
128
+ parses "/:name?.?:format?", "/.bar", "name" => nil, "format" => "bar"
129
+ parses "/:name?.:format?", "/.bar", "name" => nil, "format" => "bar"
130
+ fails "/:name.:format", "/.bar"
131
+ fails "/:name.?:format?", "/.bar"
132
+
133
+ converts "/:user@?:host?", %r{\A/((?:[^@/?#%]|(?:%[^4].|%[4][^0]))+)(?:@|%40)?((?:[^@/?#%]|(?:%[^4].|%[4][^0]))+)?\z}
134
+ parses "/:user@?:host?", "/foo@bar", "user" => "foo", "host" => "bar"
135
+ parses "/:user@?:host?", "/foo.foo@bar", "user" => "foo.foo", "host" => "bar"
136
+ parses "/:user@?:host?", "/foo@bar.bar", "user" => "foo", "host" => "bar.bar"
137
+
138
+ # From https://gist.github.com/2154980#gistcomment-169469.
139
+ #
140
+ # converts "/:name(.:format)?", %r{\A/([^\.%2E/?#]+)(?:\(|%28)(?:\.|%2E)([^\.%2E/?#]+)(?:\)|%29)?\z}
141
+ # parses "/:name(.:format)?", "/foo", "name" => "foo", "format" => nil
142
+ # parses "/:name(.:format)?", "/foo.bar", "name" => "foo", "format" => "bar"
143
+ fails "/:name(.:format)?", "/foo."
144
+
145
+ parses "/:id/test.bar", "/3/test.bar", {"id" => "3"}
146
+ parses "/:id/test.bar", "/2/test.bar", {"id" => "2"}
147
+ parses "/:id/test.bar", "/2E/test.bar", {"id" => "2E"}
148
+ parses "/:id/test.bar", "/2e/test.bar", {"id" => "2e"}
149
+ parses "/:id/test.bar", "/%2E/test.bar", {"id" => "%2E"}
150
+
151
+ parses '/10/:id', '/10/test', "id" => "test"
152
+ parses '/10/:id', '/10/te.st', "id" => "te.st"
153
+
154
+ parses '/10.1/:id', '/10.1/test', "id" => "test"
155
+ parses '/10.1/:id', '/10.1/te.st', "id" => "te.st"
156
+ parses '/:foo/:id', '/10.1/te.st', "foo" => "10.1", "id" => "te.st"
157
+ parses '/:foo/:id', '/10.1.2/te.st', "foo" => "10.1.2", "id" => "te.st"
158
+ parses '/:foo.:bar/:id', '/10.1/te.st', "foo" => "10", "bar" => "1", "id" => "te.st"
159
+ fails '/:foo.:bar/:id', '/10.1.2/te.st' # We don't do crazy.
160
+
161
+ parses '/:a/:b.?:c?', '/a/b', "a" => "a", "b" => "b", "c" => nil
162
+ parses '/:a/:b.?:c?', '/a/b.c', "a" => "a", "b" => "b", "c" => "c"
163
+ parses '/:a/:b.?:c?', '/a.b/c', "a" => "a.b", "b" => "c", "c" => nil
164
+ parses '/:a/:b.?:c?', '/a.b/c.d', "a" => "a.b", "b" => "c", "c" => "d"
165
+ fails '/:a/:b.?:c?', '/a.b/c.d/e'
166
+
167
+ parses "/:file.:ext", "/pony%2ejpg", "file" => "pony", "ext" => "jpg"
168
+ parses "/:file.:ext", "/pony%E6%AD%A3%2Ejpg", "file" => "pony%E6%AD%A3", "ext" => "jpg"
169
+ parses "/:file.:ext", "/pony%e6%ad%a3%2ejpg", "file" => "pony%e6%ad%a3", "ext" => "jpg"
170
+ parses "/:file.:ext", "/pony正%2Ejpg", "file" => "pony正", "ext" => "jpg"
171
+ parses "/:file.:ext", "/pony正%2ejpg", "file" => "pony正", "ext" => "jpg"
172
+ parses "/:file.:ext", "/pony正..jpg", "file" => "pony正", "ext" => ".jpg"
173
+ fails "/:file.:ext", "/pony正.%2ejpg"
174
+
175
+ converts "/:name.:format", %r{\A/((?:[^\./?#%]|(?:%[^2].|%[2][^Ee]))+)(?:\.|%2[Ee])((?:[^/?#%]|(?:%[^2].|%[2][^Ee]))+)\z}
176
+ parses "/:name.:format", "/file.tar.gz", "name" => "file", "format" => "tar.gz"
177
+ parses "/:name.:format1.:format2", "/file.tar.gz", "name" => "file", "format1" => "tar", "format2" => "gz"
178
+ parses "/:name.:format1.:format2", "/file.temp.tar.gz", "name" => "file", "format1" => "temp", "format2" => "tar.gz"
179
+
180
+ # From issue #688.
181
+ #
182
+ parses "/articles/10.1103/:doi", "/articles/10.1103/PhysRevLett.110.026401", "doi" => "PhysRevLett.110.026401"
183
+ end