sinatra-base 1.0 → 1.4.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 (101) hide show
  1. data/.yardopts +4 -0
  2. data/AUTHORS +15 -0
  3. data/CHANGES +524 -1
  4. data/Gemfile +82 -0
  5. data/LICENSE +1 -1
  6. data/README.de.rdoc +2093 -0
  7. data/README.es.rdoc +2091 -0
  8. data/README.fr.rdoc +2116 -0
  9. data/README.hu.rdoc +607 -0
  10. data/README.jp.rdoc +514 -23
  11. data/README.pt-br.rdoc +647 -0
  12. data/README.pt-pt.rdoc +646 -0
  13. data/README.rdoc +1580 -205
  14. data/README.ru.rdoc +2015 -0
  15. data/README.zh.rdoc +1816 -0
  16. data/Rakefile +110 -44
  17. data/examples/chat.rb +61 -0
  18. data/examples/simple.rb +3 -0
  19. data/examples/stream.ru +26 -0
  20. data/lib/sinatra.rb +0 -3
  21. data/lib/sinatra/base.rb +923 -393
  22. data/lib/sinatra/main.rb +9 -7
  23. data/lib/sinatra/showexceptions.rb +37 -4
  24. data/lib/sinatra/version.rb +3 -0
  25. data/sinatra-base.gemspec +15 -91
  26. data/test/base_test.rb +2 -2
  27. data/test/builder_test.rb +32 -2
  28. data/test/coffee_test.rb +92 -0
  29. data/test/contest.rb +62 -28
  30. data/test/creole_test.rb +65 -0
  31. data/test/delegator_test.rb +162 -0
  32. data/test/encoding_test.rb +20 -0
  33. data/test/erb_test.rb +25 -2
  34. data/test/extensions_test.rb +1 -1
  35. data/test/filter_test.rb +226 -8
  36. data/test/haml_test.rb +8 -2
  37. data/test/helper.rb +47 -0
  38. data/test/helpers_test.rb +1287 -80
  39. data/test/integration/app.rb +62 -0
  40. data/test/integration_helper.rb +208 -0
  41. data/test/integration_test.rb +82 -0
  42. data/test/less_test.rb +36 -6
  43. data/test/liquid_test.rb +59 -0
  44. data/test/mapped_error_test.rb +84 -7
  45. data/test/markaby_test.rb +80 -0
  46. data/test/markdown_test.rb +81 -0
  47. data/test/middleware_test.rb +1 -1
  48. data/test/nokogiri_test.rb +69 -0
  49. data/test/rack_test.rb +45 -0
  50. data/test/radius_test.rb +59 -0
  51. data/test/rdoc_test.rb +66 -0
  52. data/test/readme_test.rb +136 -0
  53. data/test/request_test.rb +13 -1
  54. data/test/response_test.rb +21 -2
  55. data/test/result_test.rb +5 -5
  56. data/test/route_added_hook_test.rb +1 -1
  57. data/test/routing_test.rb +328 -13
  58. data/test/sass_test.rb +48 -18
  59. data/test/scss_test.rb +88 -0
  60. data/test/server_test.rb +4 -3
  61. data/test/settings_test.rb +191 -21
  62. data/test/sinatra_test.rb +5 -1
  63. data/test/slim_test.rb +88 -0
  64. data/test/static_test.rb +89 -5
  65. data/test/streaming_test.rb +140 -0
  66. data/test/templates_test.rb +143 -4
  67. data/test/textile_test.rb +65 -0
  68. data/test/views/a/in_a.str +1 -0
  69. data/test/views/ascii.erb +2 -0
  70. data/test/views/b/in_b.str +1 -0
  71. data/test/views/calc.html.erb +1 -0
  72. data/test/views/explicitly_nested.str +1 -0
  73. data/test/views/hello.coffee +1 -0
  74. data/test/views/hello.creole +1 -0
  75. data/test/views/hello.liquid +1 -0
  76. data/test/views/hello.mab +1 -0
  77. data/test/views/hello.md +1 -0
  78. data/test/views/hello.nokogiri +1 -0
  79. data/test/views/hello.radius +1 -0
  80. data/test/views/hello.rdoc +1 -0
  81. data/test/views/hello.sass +1 -1
  82. data/test/views/hello.scss +3 -0
  83. data/test/views/hello.slim +1 -0
  84. data/test/views/hello.str +1 -0
  85. data/test/views/hello.textile +1 -0
  86. data/test/views/hello.yajl +1 -0
  87. data/test/views/layout2.liquid +2 -0
  88. data/test/views/layout2.mab +2 -0
  89. data/test/views/layout2.nokogiri +3 -0
  90. data/test/views/layout2.radius +2 -0
  91. data/test/views/layout2.slim +3 -0
  92. data/test/views/layout2.str +2 -0
  93. data/test/views/nested.str +1 -0
  94. data/test/views/utf8.erb +2 -0
  95. data/test/yajl_test.rb +80 -0
  96. metadata +126 -91
  97. data/lib/sinatra/tilt.rb +0 -746
  98. data/test/erubis_test.rb +0 -82
  99. data/test/views/error.erubis +0 -3
  100. data/test/views/hello.erubis +0 -1
  101. data/test/views/layout2.erubis +0 -2
@@ -0,0 +1,62 @@
1
+ $stderr.puts "loading"
2
+ require 'sinatra'
3
+
4
+ configure do
5
+ set :foo, :bar
6
+ end
7
+
8
+ get '/app_file' do
9
+ content_type :txt
10
+ settings.app_file
11
+ end
12
+
13
+ get '/ping' do
14
+ 'pong'
15
+ end
16
+
17
+ get '/stream' do
18
+ stream do |out|
19
+ sleep 0.1
20
+ out << "a"
21
+ sleep 1.2
22
+ out << "b"
23
+ end
24
+ end
25
+
26
+ get '/mainonly' do
27
+ object = Object.new
28
+ begin
29
+ object.send(:get, '/foo') { }
30
+ 'false'
31
+ rescue NameError
32
+ 'true'
33
+ end
34
+ end
35
+
36
+ set :out, nil
37
+ get '/async' do
38
+ stream(:keep_open) { |o| (settings.out = o) << "hi!" }
39
+ end
40
+
41
+ get '/send' do
42
+ settings.out << params[:msg] if params[:msg]
43
+ settings.out.close if params[:close]
44
+ "ok"
45
+ end
46
+
47
+ class Subclass < Sinatra::Base
48
+ set :out, nil
49
+ get '/subclass/async' do
50
+ stream(:keep_open) { |o| (settings.out = o) << "hi!" }
51
+ end
52
+
53
+ get '/subclass/send' do
54
+ settings.out << params[:msg] if params[:msg]
55
+ settings.out.close if params[:close]
56
+ "ok"
57
+ end
58
+ end
59
+
60
+ use Subclass
61
+
62
+ $stderr.puts "starting"
@@ -0,0 +1,208 @@
1
+ require 'sinatra/base'
2
+ require 'rbconfig'
3
+ require 'open-uri'
4
+ require 'net/http'
5
+
6
+ module IntegrationHelper
7
+ class BaseServer
8
+ extend Enumerable
9
+ attr_accessor :server, :port, :pipe
10
+ alias name server
11
+
12
+ def self.all
13
+ @all ||= []
14
+ end
15
+
16
+ def self.each(&block)
17
+ all.each(&block)
18
+ end
19
+
20
+ def self.run(server, port)
21
+ new(server, port).run
22
+ end
23
+
24
+ def app_file
25
+ File.expand_path('../integration/app.rb', __FILE__)
26
+ end
27
+
28
+ def environment
29
+ "development"
30
+ end
31
+
32
+ def initialize(server, port)
33
+ @installed, @pipe, @server, @port = nil, nil, server, port
34
+ Server.all << self
35
+ end
36
+
37
+ def run
38
+ return unless installed?
39
+ kill
40
+ @log = ""
41
+ @pipe = IO.popen(command)
42
+ @started = Time.now
43
+ warn "#{server} up and running on port #{port}" if ping
44
+ at_exit { kill }
45
+ end
46
+
47
+ def expect(str)
48
+ return if log.size < str.size or log[0, str.size] == str
49
+ raise "Server did not start properly:\n\n#{log}"
50
+ end
51
+
52
+ def ping(timeout = 10)
53
+ loop do
54
+ return if alive?
55
+ if Time.now - @started > timeout
56
+ $stderr.puts command, log
57
+ get('/ping')
58
+ else
59
+ expect "loading"
60
+ sleep 0.1
61
+ end
62
+ end
63
+ end
64
+
65
+ def alive?
66
+ 3.times { get('/ping') }
67
+ true
68
+ rescue Errno::ECONNREFUSED, Errno::ECONNRESET, EOFError, SystemCallError => error
69
+ false
70
+ end
71
+
72
+ def get_stream(url = "/stream", &block)
73
+ Net::HTTP.start '127.0.0.1', port do |http|
74
+ request = Net::HTTP::Get.new url
75
+ http.request request do |response|
76
+ response.read_body(&block)
77
+ end
78
+ end
79
+ end
80
+
81
+ def get(url)
82
+ open("http://127.0.0.1:#{port}#{url}").read
83
+ end
84
+
85
+ def log
86
+ @log ||= ""
87
+ loop { @log << @pipe.read_nonblock(1) }
88
+ rescue Exception
89
+ @log
90
+ end
91
+
92
+ def installed?
93
+ return @installed unless @installed.nil?
94
+ require server
95
+ @installed = true
96
+ rescue LoadError
97
+ warn "#{server} is not installed, skipping integration tests"
98
+ @installed = false
99
+ end
100
+
101
+ def command
102
+ @command ||= begin
103
+ cmd = ["RACK_ENV=#{environment}", "exec"]
104
+ if RbConfig.respond_to? :ruby
105
+ cmd << RbConfig.ruby.inspect
106
+ else
107
+ file, dir = RbConfig::CONFIG.values_at('ruby_install_name', 'bindir')
108
+ cmd << File.expand_path(file, dir).inspect
109
+ end
110
+ cmd << "-I" << File.expand_path('../../lib', __FILE__).inspect
111
+ cmd << app_file.inspect << '-s' << server << '-o' << '127.0.0.1' << '-p' << port
112
+ cmd << "-e" << environment.to_s << '2>&1'
113
+ cmd.join " "
114
+ end
115
+ end
116
+
117
+ def kill
118
+ return unless pipe
119
+ Process.kill("KILL", pipe.pid)
120
+ rescue NotImplementedError
121
+ system "kill -9 #{pipe.pid}"
122
+ rescue Errno::ESRCH
123
+ end
124
+
125
+ def webrick?
126
+ name.to_s == "webrick"
127
+ end
128
+ end
129
+
130
+ if RUBY_ENGINE == "jruby"
131
+ class JRubyServer < BaseServer
132
+ def start_vm
133
+ require 'java'
134
+ # Create a new container, set load paths and env
135
+ # SINGLETHREAD means create a new runtime
136
+ vm = org.jruby.embed.ScriptingContainer.new(org.jruby.embed.LocalContextScope::SINGLETHREAD)
137
+ vm.load_paths = [File.expand_path('../../lib', __FILE__)]
138
+ vm.environment = ENV.merge('RACK_ENV' => environment.to_s)
139
+
140
+ # This ensures processing of RUBYOPT which activates Bundler
141
+ vm.provider.ruby_instance_config.process_arguments []
142
+ vm.argv = ['-s', server.to_s, '-o', '127.0.0.1', '-p', port.to_s, '-e', environment.to_s]
143
+
144
+ # Set stdout/stderr so we can retrieve log
145
+ @pipe = java.io.ByteArrayOutputStream.new
146
+ vm.output = java.io.PrintStream.new(@pipe)
147
+ vm.error = java.io.PrintStream.new(@pipe)
148
+
149
+ Thread.new do
150
+ # Hack to ensure that Kernel#caller has the same info as
151
+ # when run from command-line, for Sintra::Application.app_file.
152
+ # Also, line numbers are zero-based in JRuby's parser
153
+ vm.provider.runtime.current_context.set_file_and_line(app_file, 0)
154
+ # Run the app
155
+ vm.run_scriptlet org.jruby.embed.PathType::ABSOLUTE, app_file
156
+ # terminate launches at_exit hooks which start server
157
+ vm.terminate
158
+ end
159
+ end
160
+
161
+ def run
162
+ return unless installed?
163
+ kill
164
+ @thread = start_vm
165
+ @started = Time.now
166
+ warn "#{server} up and running on port #{port}" if ping
167
+ at_exit { kill }
168
+ end
169
+
170
+ def log
171
+ String.from_java_bytes @pipe.to_byte_array
172
+ end
173
+
174
+ def kill
175
+ @thread.kill if @thread
176
+ @thread = nil
177
+ end
178
+ end
179
+ Server = JRubyServer
180
+ else
181
+ Server = BaseServer
182
+ end
183
+
184
+ def it(message, &block)
185
+ Server.each do |server|
186
+ next unless server.installed?
187
+ super "with #{server.name}: #{message}" do
188
+ self.server = server
189
+ server.run unless server.alive?
190
+ begin
191
+ instance_eval(&block)
192
+ rescue Exception => error
193
+ server.kill
194
+ raise error
195
+ end
196
+ end
197
+ end
198
+ end
199
+
200
+ def self.extend_object(obj)
201
+ super
202
+
203
+ base_port = 5000 + Process.pid % 100
204
+ Sinatra::Base.server.each_with_index do |server, index|
205
+ Server.run(server, 5000+index)
206
+ end
207
+ end
208
+ end
@@ -0,0 +1,82 @@
1
+ require File.expand_path('../helper', __FILE__)
2
+ require File.expand_path('../integration_helper', __FILE__)
3
+ require 'timeout'
4
+
5
+ # These tests start a real server and talk to it over TCP.
6
+ # Every test runs with every detected server.
7
+ #
8
+ # See test/integration/app.rb for the code of the app we test against.
9
+ class IntegrationTest < Test::Unit::TestCase
10
+ extend IntegrationHelper
11
+ attr_accessor :server
12
+
13
+ it('sets the app_file') { assert_equal server.app_file, server.get("/app_file") }
14
+ it('only extends main') { assert_equal "true", server.get("/mainonly") }
15
+
16
+ it 'logs once in development mode' do
17
+ random = "%064x" % Kernel.rand(2**256-1)
18
+ server.get "/ping?x=#{random}"
19
+ count = server.log.scan("GET /ping?x=#{random}").count
20
+ server.webrick? ? assert(count > 0) : assert_equal(1, count)
21
+ end
22
+
23
+ it 'streams' do
24
+ next if server.webrick?
25
+ times, chunks = [Time.now], []
26
+ server.get_stream do |chunk|
27
+ next if chunk.empty?
28
+ chunks << chunk
29
+ times << Time.now
30
+ end
31
+ assert_equal ["a", "b"], chunks
32
+ assert times[1] - times[0] < 1
33
+ assert times[2] - times[1] > 1
34
+ end
35
+
36
+ it 'streams async' do
37
+ next unless server.name == 'thin'
38
+
39
+ Timeout.timeout(3) do
40
+ chunks = []
41
+ server.get_stream '/async' do |chunk|
42
+ next if chunk.empty?
43
+ chunks << chunk
44
+ case chunk
45
+ when "hi!" then server.get "/send?msg=hello"
46
+ when "hello" then server.get "/send?close=1"
47
+ end
48
+ end
49
+
50
+ assert_equal ['hi!', 'hello'], chunks
51
+ end
52
+ end
53
+
54
+ it 'streams async from subclass' do
55
+ next unless server.name == 'thin'
56
+
57
+ Timeout.timeout(3) do
58
+ chunks = []
59
+ server.get_stream '/subclass/async' do |chunk|
60
+ next if chunk.empty?
61
+ chunks << chunk
62
+ case chunk
63
+ when "hi!" then server.get "/subclass/send?msg=hello"
64
+ when "hello" then server.get "/subclass/send?close=1"
65
+ end
66
+ end
67
+
68
+ assert_equal ['hi!', 'hello'], chunks
69
+ end
70
+ end
71
+
72
+
73
+ it 'starts the correct server' do
74
+ exp = %r{
75
+ ==\sSinatra/#{Sinatra::VERSION}\s
76
+ has\staken\sthe\sstage\son\s\d+\sfor\sdevelopment\s
77
+ with\sbackup\sfrom\s#{server}
78
+ }ix
79
+
80
+ assert_match exp, server.log
81
+ end
82
+ end
@@ -1,31 +1,57 @@
1
- require File.dirname(__FILE__) + '/helper'
1
+ require File.expand_path('../helper', __FILE__)
2
+
3
+ begin
2
4
  require 'less'
3
5
 
4
6
  class LessTest < Test::Unit::TestCase
5
- def less_app(&block)
7
+ def less_app(options = {}, &block)
6
8
  mock_app {
7
9
  set :views, File.dirname(__FILE__) + '/views'
10
+ set options
8
11
  get '/', &block
9
12
  }
10
13
  get '/'
11
14
  end
12
15
 
13
16
  it 'renders inline Less strings' do
14
- less_app { less "@white_color: #fff; #main { background-color: @white_color }"}
17
+ less_app { less "@white_color: #fff; #main { background-color: @white_color }" }
18
+ assert ok?
19
+ assert_equal "#main{background-color:#ffffff;}", body.gsub(/\s/, "")
20
+ end
21
+
22
+ it 'defaults content type to css' do
23
+ less_app { less "@white_color: #fff; #main { background-color: @white_color }" }
24
+ assert ok?
25
+ assert_equal "text/css;charset=utf-8", response['Content-Type']
26
+ end
27
+
28
+ it 'defaults allows setting content type per route' do
29
+ less_app do
30
+ content_type :html
31
+ less "@white_color: #fff; #main { background-color: @white_color }"
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
+ less_app(:less => { :content_type => 'html' }) do
39
+ less "@white_color: #fff; #main { background-color: @white_color }"
40
+ end
15
41
  assert ok?
16
- assert_equal "#main { background-color: #ffffff; }\n", body
42
+ assert_equal "text/html;charset=utf-8", response['Content-Type']
17
43
  end
18
44
 
19
45
  it 'renders .less files in views path' do
20
46
  less_app { less :hello }
21
47
  assert ok?
22
- assert_equal "#main { background-color: #ffffff; }\n", body
48
+ assert_equal "#main{background-color:#ffffff;}", body.gsub(/\s/, "")
23
49
  end
24
50
 
25
51
  it 'ignores the layout option' do
26
52
  less_app { less :hello, :layout => :layout2 }
27
53
  assert ok?
28
- assert_equal "#main { background-color: #ffffff; }\n", body
54
+ assert_equal "#main{background-color:#ffffff;}", body.gsub(/\s/, "")
29
55
  end
30
56
 
31
57
  it "raises error if template not found" do
@@ -35,3 +61,7 @@ class LessTest < Test::Unit::TestCase
35
61
  assert_raise(Errno::ENOENT) { get('/') }
36
62
  end
37
63
  end
64
+
65
+ rescue LoadError
66
+ warn "#{$!.to_s}: skipping less tests"
67
+ end
@@ -0,0 +1,59 @@
1
+ require File.expand_path('../helper', __FILE__)
2
+
3
+ begin
4
+ require 'liquid'
5
+
6
+ class LiquidTest < Test::Unit::TestCase
7
+ def liquid_app(&block)
8
+ mock_app do
9
+ set :views, File.dirname(__FILE__) + '/views'
10
+ get '/', &block
11
+ end
12
+ get '/'
13
+ end
14
+
15
+ it 'renders inline liquid strings' do
16
+ liquid_app { liquid '<h1>Hiya</h1>' }
17
+ assert ok?
18
+ assert_equal "<h1>Hiya</h1>", body
19
+ end
20
+
21
+ it 'renders .liquid files in views path' do
22
+ liquid_app { liquid :hello }
23
+ assert ok?
24
+ assert_equal "<h1>Hello From Liquid</h1>\n", body
25
+ end
26
+
27
+ it "renders with inline layouts" do
28
+ mock_app do
29
+ layout { "<h1>THIS. IS. {{ yield }}</h1>" }
30
+ get('/') { liquid '<EM>SPARTA</EM>' }
31
+ end
32
+ get '/'
33
+ assert ok?
34
+ assert_equal "<h1>THIS. IS. <EM>SPARTA</EM></h1>", body
35
+ end
36
+
37
+ it "renders with file layouts" do
38
+ liquid_app { liquid 'Hello World', :layout => :layout2 }
39
+ assert ok?
40
+ assert_equal "<h1>Liquid Layout!</h1>\n<p>Hello World</p>\n", body
41
+ end
42
+
43
+ it "raises error if template not found" do
44
+ mock_app { get('/') { liquid :no_such_template } }
45
+ assert_raise(Errno::ENOENT) { get('/') }
46
+ end
47
+
48
+ it "allows passing locals" do
49
+ liquid_app do
50
+ liquid '{{ value }}', :locals => { :value => 'foo' }
51
+ end
52
+ assert ok?
53
+ assert_equal 'foo', body
54
+ end
55
+ end
56
+
57
+ rescue LoadError
58
+ warn "#{$!.to_s}: skipping liquid tests"
59
+ end