deas 0.38.0 → 0.39.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.
- data/Gemfile +2 -1
- data/{LICENSE.txt → LICENSE} +0 -0
- data/deas.gemspec +4 -3
- data/lib/deas/deas_runner.rb +17 -12
- data/lib/deas/handler_proxy.rb +14 -8
- data/lib/deas/redirect_proxy.rb +12 -12
- data/lib/deas/runner.rb +169 -19
- data/lib/deas/server.rb +14 -10
- data/lib/deas/template_source.rb +2 -2
- data/lib/deas/test_runner.rb +59 -60
- data/lib/deas/version.rb +1 -1
- data/lib/deas/view_handler.rb +55 -30
- data/test/support/empty_view_handler.rb +7 -0
- data/test/support/factory.rb +5 -0
- data/test/support/fake_request.rb +29 -0
- data/test/support/fake_sinatra_call.rb +11 -16
- data/test/support/file1.txt +1 -0
- data/test/support/file2.txt +1 -0
- data/test/support/routes.rb +7 -7
- data/test/support/show.html +0 -0
- data/test/support/show.json +0 -0
- data/test/system/{rack_tests.rb → deas_tests.rb} +19 -14
- data/test/unit/deas_runner_tests.rb +209 -20
- data/test/unit/handler_proxy_tests.rb +27 -19
- data/test/unit/redirect_proxy_tests.rb +32 -33
- data/test/unit/respond_with_proxy_tests.rb +1 -2
- data/test/unit/route_proxy_tests.rb +1 -1
- data/test/unit/route_tests.rb +1 -1
- data/test/unit/router_tests.rb +5 -5
- data/test/unit/runner_tests.rb +619 -76
- data/test/unit/server_tests.rb +6 -1
- data/test/unit/sinatra_app_tests.rb +1 -1
- data/test/unit/test_runner_tests.rb +377 -106
- data/test/unit/url_tests.rb +1 -1
- data/test/unit/view_handler_tests.rb +325 -182
- metadata +43 -24
- data/lib/deas/sinatra_runner.rb +0 -55
- data/lib/deas/test_helpers.rb +0 -23
- data/test/support/view_handlers.rb +0 -83
- data/test/unit/sinatra_runner_tests.rb +0 -79
- data/test/unit/test_helpers_tests.rb +0 -53
data/Gemfile
CHANGED
data/{LICENSE.txt → LICENSE}
RENAMED
|
File without changes
|
data/deas.gemspec
CHANGED
|
@@ -18,9 +18,10 @@ Gem::Specification.new do |gem|
|
|
|
18
18
|
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
|
19
19
|
gem.require_paths = ["lib"]
|
|
20
20
|
|
|
21
|
-
gem.add_dependency("
|
|
22
|
-
gem.add_dependency("
|
|
23
|
-
gem.add_dependency("
|
|
21
|
+
gem.add_dependency("much-plugin", ["~> 0.1"])
|
|
22
|
+
gem.add_dependency("ns-options", ["~> 1.1", ">= 1.1.4"])
|
|
23
|
+
gem.add_dependency("rack", ["~> 1.5"])
|
|
24
|
+
gem.add_dependency("sinatra", ["~> 1.4"])
|
|
24
25
|
|
|
25
26
|
gem.add_development_dependency("assert", ["~> 2.15"])
|
|
26
27
|
gem.add_development_dependency("assert-rack-test")
|
data/lib/deas/deas_runner.rb
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
require 'rack/utils'
|
|
1
2
|
require 'deas/runner'
|
|
2
3
|
|
|
3
4
|
module Deas
|
|
@@ -5,25 +6,29 @@ module Deas
|
|
|
5
6
|
class DeasRunner < Runner
|
|
6
7
|
|
|
7
8
|
def initialize(handler_class, args = nil)
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
args ||= {}
|
|
10
|
+
super(
|
|
11
|
+
handler_class,
|
|
12
|
+
args.merge(:params => NormalizedParams.new(args[:params]).value)
|
|
13
|
+
)
|
|
11
14
|
end
|
|
12
15
|
|
|
13
16
|
def run
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
catch(:halt) do
|
|
18
|
+
self.handler.deas_run_callback 'before'
|
|
19
|
+
catch(:halt){ self.handler.deas_init; self.handler.deas_run }
|
|
20
|
+
self.handler.deas_run_callback 'after'
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
self.to_rack.tap do |(status, headers, body)|
|
|
24
|
+
headers['Content-Length'] ||= body.inject(0) do |length, part|
|
|
25
|
+
length + Rack::Utils.bytesize(part)
|
|
26
|
+
end.to_s
|
|
27
|
+
end
|
|
19
28
|
end
|
|
20
29
|
|
|
21
30
|
private
|
|
22
31
|
|
|
23
|
-
def run_callbacks(callbacks)
|
|
24
|
-
callbacks.each{|proc| self.handler.instance_eval(&proc) }
|
|
25
|
-
end
|
|
26
|
-
|
|
27
32
|
class NormalizedParams < Deas::Runner::NormalizedParams
|
|
28
33
|
def file_type?(value)
|
|
29
34
|
value.kind_of?(::Tempfile)
|
data/lib/deas/handler_proxy.rb
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
require 'deas/exceptions'
|
|
2
|
-
require 'deas/
|
|
2
|
+
require 'deas/deas_runner'
|
|
3
3
|
|
|
4
4
|
module Deas
|
|
5
5
|
|
|
@@ -16,15 +16,21 @@ module Deas
|
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
def run(server_data, sinatra_call)
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
19
|
+
# these are not part of Deas' intended behavior and route matching
|
|
20
|
+
# they are side-effects of using Sinatra. remove them so they won't
|
|
21
|
+
# be relied upon in Deas apps.
|
|
22
|
+
sinatra_call.params.delete(:splat)
|
|
23
|
+
sinatra_call.params.delete('splat')
|
|
24
|
+
sinatra_call.params.delete(:captures)
|
|
25
|
+
sinatra_call.params.delete('captures')
|
|
26
|
+
|
|
27
|
+
runner = DeasRunner.new(self.handler_class, {
|
|
25
28
|
:logger => server_data.logger,
|
|
26
29
|
:router => server_data.router,
|
|
27
|
-
:template_source => server_data.template_source
|
|
30
|
+
:template_source => server_data.template_source,
|
|
31
|
+
:request => sinatra_call.request,
|
|
32
|
+
:session => sinatra_call.session,
|
|
33
|
+
:params => sinatra_call.params
|
|
28
34
|
})
|
|
29
35
|
|
|
30
36
|
runner.request.env.tap do |env|
|
data/lib/deas/redirect_proxy.rb
CHANGED
|
@@ -8,7 +8,7 @@ module Deas
|
|
|
8
8
|
|
|
9
9
|
attr_reader :handler_class_name, :handler_class
|
|
10
10
|
|
|
11
|
-
def initialize(router,
|
|
11
|
+
def initialize(router, location = nil, &block)
|
|
12
12
|
@handler_class = Class.new do
|
|
13
13
|
include Deas::ViewHandler
|
|
14
14
|
|
|
@@ -17,34 +17,34 @@ module Deas
|
|
|
17
17
|
@router = value
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
-
def self.
|
|
21
|
-
def self.
|
|
22
|
-
@
|
|
20
|
+
def self.redirect_location; @redirect_location; end
|
|
21
|
+
def self.redirect_location=(value)
|
|
22
|
+
@redirect_location = value
|
|
23
23
|
end
|
|
24
24
|
|
|
25
25
|
def self.name; 'Deas::RedirectHandler'; end
|
|
26
26
|
|
|
27
|
-
attr_reader :
|
|
27
|
+
attr_reader :redirect_location
|
|
28
28
|
|
|
29
29
|
def init!
|
|
30
|
-
@
|
|
31
|
-
self.instance_eval(&self.class.
|
|
30
|
+
@redirect_location = self.class.router.prepend_base_url(
|
|
31
|
+
self.instance_eval(&self.class.redirect_location)
|
|
32
32
|
)
|
|
33
33
|
end
|
|
34
34
|
|
|
35
35
|
def run!
|
|
36
|
-
redirect @
|
|
36
|
+
redirect @redirect_location
|
|
37
37
|
end
|
|
38
38
|
|
|
39
39
|
end
|
|
40
40
|
|
|
41
41
|
@handler_class.router = router
|
|
42
|
-
@handler_class.
|
|
42
|
+
@handler_class.redirect_location = if location.nil?
|
|
43
43
|
block
|
|
44
|
-
elsif
|
|
45
|
-
proc{
|
|
44
|
+
elsif location.kind_of?(Deas::Url)
|
|
45
|
+
proc{ location.path_for(params) }
|
|
46
46
|
else
|
|
47
|
-
proc{
|
|
47
|
+
proc{ location }
|
|
48
48
|
end
|
|
49
49
|
@handler_class_name = @handler_class.name
|
|
50
50
|
end
|
data/lib/deas/runner.rb
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
require 'rack/utils'
|
|
2
|
+
require 'pathname'
|
|
2
3
|
require 'deas/logger'
|
|
3
4
|
require 'deas/router'
|
|
4
5
|
require 'deas/template_source'
|
|
@@ -7,40 +8,115 @@ module Deas
|
|
|
7
8
|
|
|
8
9
|
class Runner
|
|
9
10
|
|
|
11
|
+
DEFAULT_MIME_TYPE = 'application/octet-stream'.freeze
|
|
12
|
+
DEFAULT_CHARSET = 'utf-8'.freeze
|
|
13
|
+
DEFAULT_STATUS = 200.freeze
|
|
14
|
+
DEFAULT_BODY = [].freeze
|
|
15
|
+
|
|
10
16
|
attr_reader :handler_class, :handler
|
|
11
|
-
attr_reader :
|
|
12
|
-
attr_reader :
|
|
17
|
+
attr_reader :logger, :router, :template_source
|
|
18
|
+
attr_reader :request, :session, :params
|
|
13
19
|
|
|
14
20
|
def initialize(handler_class, args = nil)
|
|
21
|
+
@status, @headers, @body = nil, Rack::Utils::HeaderHash.new, nil
|
|
22
|
+
|
|
23
|
+
args ||= {}
|
|
24
|
+
@logger = args[:logger] || Deas::NullLogger.new
|
|
25
|
+
@router = args[:router] || Deas::Router.new
|
|
26
|
+
@template_source = args[:template_source] || Deas::NullTemplateSource.new
|
|
27
|
+
@request = args[:request]
|
|
28
|
+
@session = args[:session]
|
|
29
|
+
@params = args[:params] || {}
|
|
30
|
+
|
|
15
31
|
@handler_class = handler_class
|
|
16
32
|
@handler = @handler_class.new(self)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def run
|
|
36
|
+
raise NotImplementedError
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def to_rack
|
|
40
|
+
[self.status || DEFAULT_STATUS, self.headers.to_hash, self.body || DEFAULT_BODY]
|
|
41
|
+
end
|
|
17
42
|
|
|
18
|
-
|
|
19
|
-
@
|
|
20
|
-
@
|
|
21
|
-
@session = a[:session]
|
|
22
|
-
@params = a[:params] || {}
|
|
23
|
-
@logger = a[:logger] || Deas::NullLogger.new
|
|
24
|
-
@router = a[:router] || Deas::Router.new
|
|
25
|
-
@template_source = a[:template_source] || Deas::NullTemplateSource.new
|
|
43
|
+
def status(value = nil)
|
|
44
|
+
@status = value if !value.nil?
|
|
45
|
+
@status
|
|
26
46
|
end
|
|
27
47
|
|
|
28
|
-
def
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
def headers(*args); raise NotImplementedError; end
|
|
33
|
-
def send_file(*args); raise NotImplementedError; end
|
|
48
|
+
def headers(value = nil)
|
|
49
|
+
@headers.merge!(value) if !value.nil?
|
|
50
|
+
@headers
|
|
51
|
+
end
|
|
34
52
|
|
|
35
|
-
|
|
36
|
-
|
|
53
|
+
def body(value = nil)
|
|
54
|
+
if !value.nil?
|
|
55
|
+
unless value.respond_to?(:each)
|
|
56
|
+
raise ArgumentError, "this body value (class `#{value.class}`), does not "\
|
|
57
|
+
"respond to `each`."
|
|
58
|
+
end
|
|
59
|
+
@body = value
|
|
60
|
+
end
|
|
61
|
+
@body
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def content_type(extname, params = nil)
|
|
65
|
+
self.headers['Content-Type'] = get_content_type(extname, params)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def halt(*args)
|
|
69
|
+
self.status(args.shift) if args.first.instance_of?(::Fixnum)
|
|
70
|
+
self.headers(args.shift) if args.first.kind_of?(::Hash)
|
|
71
|
+
self.body(args.shift) if args.first.respond_to?(:each)
|
|
72
|
+
throw :halt
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def redirect(location, *halt_args)
|
|
76
|
+
self.status(302)
|
|
77
|
+
self.headers['Location'] = get_absolute_url(location)
|
|
78
|
+
halt(*halt_args)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def send_file(file_path, opts = nil)
|
|
82
|
+
path_name = Pathname.new(file_path)
|
|
83
|
+
self.halt(404, []) if !path_name.exist?
|
|
84
|
+
|
|
85
|
+
env = self.request.env
|
|
86
|
+
mtime = path_name.mtime.httpdate.to_s
|
|
87
|
+
self.halt(304, []) if env['HTTP_IF_MODIFIED_SINCE'] == mtime
|
|
88
|
+
self.headers['Last-Modified'] ||= mtime
|
|
89
|
+
|
|
90
|
+
self.headers['Content-Type'] ||= get_content_type(path_name.extname)
|
|
91
|
+
|
|
92
|
+
opts ||= {}
|
|
93
|
+
disposition = opts[:disposition]
|
|
94
|
+
filename = opts[:filename]
|
|
95
|
+
disposition ||= 'attachment' if !filename.nil?
|
|
96
|
+
filename ||= path_name.basename
|
|
97
|
+
if !disposition.nil?
|
|
98
|
+
self.headers['Content-Disposition'] ||= "#{disposition};filename=\"#{filename}\""
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
sfb = SendFileBody.new(env, path_name)
|
|
102
|
+
self.body(sfb)
|
|
103
|
+
self.headers['Content-Length'] ||= sfb.size.to_s
|
|
104
|
+
self.headers['Content-Range'] ||= sfb.content_range if sfb.partial?
|
|
105
|
+
self.status(sfb.partial? ? 206 : 200)
|
|
106
|
+
|
|
107
|
+
self.halt # be consistent with halts above - `send_file` always halts
|
|
108
|
+
end
|
|
37
109
|
|
|
38
110
|
def render(template_name, locals = nil)
|
|
39
111
|
source_render(self.template_source, template_name, locals)
|
|
40
112
|
end
|
|
41
113
|
|
|
42
114
|
def source_render(source, template_name, locals = nil)
|
|
43
|
-
|
|
115
|
+
self.headers['Content-Type'] ||= get_content_type(
|
|
116
|
+
File.extname(template_name),
|
|
117
|
+
'charset' => DEFAULT_CHARSET
|
|
118
|
+
)
|
|
119
|
+
self.body(source.render(template_name, self.handler, locals || {}))
|
|
44
120
|
end
|
|
45
121
|
|
|
46
122
|
def partial(template_name, locals = nil)
|
|
@@ -51,6 +127,19 @@ module Deas
|
|
|
51
127
|
source.partial(template_name, locals || {})
|
|
52
128
|
end
|
|
53
129
|
|
|
130
|
+
private
|
|
131
|
+
|
|
132
|
+
def get_content_type(extname, params = nil)
|
|
133
|
+
[ Rack::Mime.mime_type(extname, DEFAULT_MIME_TYPE),
|
|
134
|
+
params ? params.map{ |k,v| "#{k}=#{v}" }.join(',') : nil
|
|
135
|
+
].compact.join(';')
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def get_absolute_url(url)
|
|
139
|
+
return url if url =~ /\A[A-z][A-z0-9\+\.\-]*:/
|
|
140
|
+
File.join("#{request.env['rack.url_scheme']}://#{request.env['HTTP_HOST']}", url)
|
|
141
|
+
end
|
|
142
|
+
|
|
54
143
|
class NormalizedParams
|
|
55
144
|
|
|
56
145
|
attr_reader :value
|
|
@@ -73,6 +162,67 @@ module Deas
|
|
|
73
162
|
|
|
74
163
|
end
|
|
75
164
|
|
|
165
|
+
class SendFileBody
|
|
166
|
+
|
|
167
|
+
# this class borrows from the body range handling in Rack::File.
|
|
168
|
+
|
|
169
|
+
CHUNK_SIZE = (8*1024).freeze # 8k
|
|
170
|
+
|
|
171
|
+
attr_reader :path_name, :size, :content_range
|
|
172
|
+
|
|
173
|
+
def initialize(env, path_name)
|
|
174
|
+
@path_name = path_name
|
|
175
|
+
|
|
176
|
+
file_size = @path_name.size? || Rack::Utils.bytesize(path_name.read)
|
|
177
|
+
ranges = Rack::Utils.byte_ranges(env, file_size)
|
|
178
|
+
if ranges.nil? || ranges.empty? || ranges.length > 1
|
|
179
|
+
# No ranges or multiple ranges are not supported
|
|
180
|
+
@range = 0..file_size-1
|
|
181
|
+
@content_range = nil
|
|
182
|
+
else
|
|
183
|
+
# single range
|
|
184
|
+
@range = ranges[0]
|
|
185
|
+
@content_range = "bytes #{@range.begin}-#{@range.end}/#{file_size}"
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
@size = self.range_end - self.range_begin + 1
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def partial?
|
|
192
|
+
!@content_range.nil?
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def range_begin; @range.begin; end
|
|
196
|
+
def range_end; @range.end; end
|
|
197
|
+
|
|
198
|
+
def each
|
|
199
|
+
@path_name.open("rb") do |io|
|
|
200
|
+
io.seek(@range.begin)
|
|
201
|
+
remaining_len = self.size
|
|
202
|
+
while remaining_len > 0
|
|
203
|
+
part = io.read([CHUNK_SIZE, remaining_len].min)
|
|
204
|
+
break if part.nil?
|
|
205
|
+
|
|
206
|
+
remaining_len -= part.length
|
|
207
|
+
yield part
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def inspect
|
|
213
|
+
"#<#{self.class}:#{'0x0%x' % (self.object_id << 1)} " \
|
|
214
|
+
"path=#{self.path_name} " \
|
|
215
|
+
"range_begin=#{self.range_begin} range_end=#{self.range_end}>"
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def ==(other_body)
|
|
219
|
+
self.path_name.to_s == other_body.path_name.to_s &&
|
|
220
|
+
self.range_begin == other_body.range_begin &&
|
|
221
|
+
self.range_end == other_body.range_end
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
end
|
|
225
|
+
|
|
76
226
|
end
|
|
77
227
|
|
|
78
228
|
end
|
data/lib/deas/server.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
require '
|
|
1
|
+
require 'much-plugin'
|
|
2
2
|
require 'ns-options'
|
|
3
3
|
require 'ns-options/boolean'
|
|
4
|
+
require 'pathname'
|
|
4
5
|
require 'deas/exceptions'
|
|
5
6
|
require 'deas/logger'
|
|
6
7
|
require 'deas/logging'
|
|
@@ -12,19 +13,20 @@ require 'deas/template_source'
|
|
|
12
13
|
module Deas
|
|
13
14
|
|
|
14
15
|
module Server
|
|
16
|
+
include MuchPlugin
|
|
15
17
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
include InstanceMethods
|
|
20
|
-
end
|
|
18
|
+
plugin_included do
|
|
19
|
+
extend ClassMethods
|
|
20
|
+
include InstanceMethods
|
|
21
21
|
end
|
|
22
22
|
|
|
23
23
|
module InstanceMethods
|
|
24
|
+
|
|
24
25
|
# TODO: once Deas is no longer powered by Sinatra, this should define an
|
|
25
26
|
# `initialize` method that builds a server instance. Right now there is
|
|
26
27
|
# a `new` class method that builds a SinatraApp which does this init
|
|
27
28
|
# behavior
|
|
29
|
+
|
|
28
30
|
end
|
|
29
31
|
|
|
30
32
|
module ClassMethods
|
|
@@ -174,8 +176,8 @@ module Deas
|
|
|
174
176
|
# server handling options
|
|
175
177
|
|
|
176
178
|
option :verbose_logging, NsOptions::Boolean, :default => true
|
|
177
|
-
option :logger
|
|
178
|
-
option :template_source
|
|
179
|
+
option :logger
|
|
180
|
+
option :template_source
|
|
179
181
|
|
|
180
182
|
attr_accessor :settings, :init_procs, :error_procs, :template_helpers
|
|
181
183
|
attr_accessor :middlewares, :router
|
|
@@ -186,8 +188,10 @@ module Deas
|
|
|
186
188
|
# Configuration class `root`, which will not update these options as
|
|
187
189
|
# expected.
|
|
188
190
|
super((values || {}).merge({
|
|
189
|
-
:public_root
|
|
190
|
-
:views_root
|
|
191
|
+
:public_root => proc{ self.root.join('public') },
|
|
192
|
+
:views_root => proc{ self.root.join('views') },
|
|
193
|
+
:logger => proc{ Deas::NullLogger.new },
|
|
194
|
+
:template_source => proc{ Deas::NullTemplateSource.new(self.root) }
|
|
191
195
|
}))
|
|
192
196
|
@settings = {}
|
|
193
197
|
@init_procs, @error_procs, @template_helpers, @middlewares = [], [], [], []
|