sinatra-acd 1.4.5

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