merb 0.1.0 → 0.2.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/README +71 -14
- data/Rakefile +20 -31
- data/examples/skeleton.tar +0 -0
- data/examples/skeleton/dist/schema/migrations/001_add_sessions_table.rb +1 -1
- data/lib/merb.rb +3 -2
- data/lib/merb/caching.rb +5 -0
- data/lib/merb/caching/action_cache.rb +56 -0
- data/lib/merb/caching/fragment_cache.rb +38 -0
- data/lib/merb/caching/store/file_cache.rb +82 -0
- data/lib/merb/caching/store/memory_cache.rb +67 -0
- data/lib/merb/core_ext.rb +1 -1
- data/lib/merb/core_ext/merb_hash.rb +35 -0
- data/lib/merb/core_ext/merb_object.rb +88 -2
- data/lib/merb/merb_controller.rb +71 -69
- data/lib/merb/merb_dispatcher.rb +72 -0
- data/lib/merb/merb_exceptions.rb +6 -1
- data/lib/merb/merb_handler.rb +19 -47
- data/lib/merb/merb_mailer.rb +1 -1
- data/lib/merb/merb_request.rb +11 -3
- data/lib/merb/merb_router.rb +113 -8
- data/lib/merb/merb_server.rb +71 -12
- data/lib/merb/merb_upload_handler.rb +8 -6
- data/lib/merb/merb_upload_progress.rb +1 -1
- data/lib/merb/merb_view_context.rb +13 -3
- data/lib/merb/mixins/basic_authentication_mixin.rb +1 -3
- data/lib/merb/mixins/controller_mixin.rb +149 -17
- data/lib/merb/mixins/form_control_mixin.rb +1 -0
- data/lib/merb/mixins/render_mixin.rb +148 -151
- data/lib/merb/mixins/responder_mixin.rb +133 -18
- data/lib/merb/mixins/view_context_mixin.rb +24 -0
- data/lib/merb/session/merb_ar_session.rb +4 -4
- data/lib/merb/session/merb_memory_session.rb +6 -5
- data/lib/merb/template.rb +10 -0
- data/lib/merb/template/erubis.rb +52 -0
- data/lib/merb/template/haml.rb +77 -0
- data/lib/merb/template/markaby.rb +48 -0
- data/lib/merb/template/xml_builder.rb +34 -0
- metadata +19 -17
- data/lib/merb/mixins/merb_status_codes.rb +0 -59
data/lib/merb/merb_mailer.rb
CHANGED
@@ -4,7 +4,7 @@ begin
|
|
4
4
|
require 'mailfactory'
|
5
5
|
rescue LoadError
|
6
6
|
puts "You need to install the mailfactory gem to use Merb::Mailer"
|
7
|
-
MERB_LOGGER.
|
7
|
+
MERB_LOGGER.warn "You need to install the mailfactory gem to use Merb::Mailer"
|
8
8
|
end
|
9
9
|
|
10
10
|
module Merb
|
data/lib/merb/merb_request.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
module Merb
|
2
2
|
|
3
3
|
class Request
|
4
|
-
|
4
|
+
attr_accessor :env
|
5
5
|
def initialize(env, method)
|
6
6
|
@env = env
|
7
7
|
@method = method
|
@@ -40,11 +40,20 @@ module Merb
|
|
40
40
|
@env['HTTPS'] == 'on' || @env['HTTP_X_FORWARDED_PROTO'] == 'https'
|
41
41
|
end
|
42
42
|
|
43
|
+
# returns the request HTTP_REFERER.
|
44
|
+
def referer
|
45
|
+
@env['HTTP_REFERER']
|
46
|
+
end
|
47
|
+
|
43
48
|
# returns he request uri.
|
44
49
|
def uri
|
45
50
|
@env['REQUEST_URI']
|
46
51
|
end
|
47
52
|
|
53
|
+
def query_string
|
54
|
+
@env['QUERY_STRING']
|
55
|
+
end
|
56
|
+
|
48
57
|
# returns the uri without the query string.
|
49
58
|
def path
|
50
59
|
uri ? uri.split('?').first : ''
|
@@ -78,7 +87,7 @@ module Merb
|
|
78
87
|
|
79
88
|
# returns the REQUEST_METHOD
|
80
89
|
def method
|
81
|
-
@method ||= @env['REQUEST_METHOD']
|
90
|
+
@method ||= @env['REQUEST_METHOD'].downcase.intern
|
82
91
|
end
|
83
92
|
|
84
93
|
# create predicate methods for querying the REQUEST_METHOD
|
@@ -92,4 +101,3 @@ module Merb
|
|
92
101
|
|
93
102
|
end
|
94
103
|
|
95
|
-
|
data/lib/merb/merb_router.rb
CHANGED
@@ -1,5 +1,9 @@
|
|
1
1
|
module Merb
|
2
|
-
|
2
|
+
begin
|
3
|
+
require 'active_support'
|
4
|
+
rescue
|
5
|
+
MERB_LOGGER.warn "You must have ActiveSupport installed to use merb restful routing\nNormal routing works fine without"
|
6
|
+
end
|
3
7
|
# Merb::RouteMatcher is the request routing mapper for the merb framework.
|
4
8
|
# You can define placeholder parts of the url with the :symbol notation.
|
5
9
|
# so r.add '/foo/:bar/baz/:id', :class => 'Bar', :method => 'foo'
|
@@ -20,6 +24,7 @@ module Merb
|
|
20
24
|
def self.prepare
|
21
25
|
@@routes = Array.new
|
22
26
|
@@compiled_statement = String.new
|
27
|
+
@@compiled_regexen = Array.new
|
23
28
|
yield self
|
24
29
|
compile_router
|
25
30
|
end
|
@@ -36,6 +41,14 @@ module Merb
|
|
36
41
|
|
37
42
|
# the final compiled lambda that gets used
|
38
43
|
# as the body of the route_request method.
|
44
|
+
def self.compiled_statement
|
45
|
+
@@compiled_statement
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.compiled_regexen
|
49
|
+
@@compiled_regexen
|
50
|
+
end
|
51
|
+
|
39
52
|
def compiled_statement
|
40
53
|
@@compiled_statement
|
41
54
|
end
|
@@ -79,18 +92,110 @@ module Merb
|
|
79
92
|
(name =~ /(\*+)(\w+)/) ? (flag = true; name = $2) : (flag = false)
|
80
93
|
count += 1
|
81
94
|
if flag
|
82
|
-
route[0].sub!(@@section_regexp, "([
|
95
|
+
route[0].sub!(@@section_regexp, "([^,?]+)")
|
83
96
|
else
|
84
|
-
route[0].sub!(@@section_regexp, "([
|
97
|
+
route[0].sub!(@@section_regexp, "([^\/,?]+)")
|
85
98
|
end
|
86
99
|
code << " @sections[:#{name}] = $#{count}\n"
|
87
|
-
end
|
88
|
-
|
100
|
+
end
|
101
|
+
@@compiled_regexen << Regexp.new(route[0])
|
102
|
+
index = @@compiled_regexen.size - 1
|
103
|
+
condition = " when @@compiled_regexen[#{index}] "
|
89
104
|
statement = "#{condition}\n#{code}"
|
90
|
-
statement << " return #{route[1].inspect}.
|
105
|
+
statement << " return #{route[1].inspect}.update(@sections)\n"
|
91
106
|
statement
|
92
107
|
end
|
93
|
-
|
108
|
+
|
109
|
+
class Resource
|
110
|
+
# TODO : come up with naming convention and generate helper
|
111
|
+
# methods for route generation. Route generation
|
112
|
+
# framework needed but needs to be simple and light
|
113
|
+
|
114
|
+
def initialize(resource, procs=[], opts={})
|
115
|
+
@resource, @procs, @opts = resource, procs, opts
|
116
|
+
@procs << proc { ::Merb::RouteMatcher.generate_resources_routes(@resource, @opts) }
|
117
|
+
end
|
118
|
+
|
119
|
+
def resources(res, opts={})
|
120
|
+
(opts[:prefix]||='') << "/#{@resource}/:#{@resource.to_s.singularize}_id"
|
121
|
+
|
122
|
+
opts[:prefix] = @opts[:prefix] + opts[:prefix]
|
123
|
+
if block_given?
|
124
|
+
yield self.class.new(res, @procs, opts)
|
125
|
+
else
|
126
|
+
@procs << proc { ::Merb::RouteMatcher.generate_resources_routes(res, opts) }
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def resource(res, opts={})
|
131
|
+
(opts[:prefix]||='') << "/#{@resource}/:#{@resource.to_s.singularize}_id"
|
132
|
+
|
133
|
+
opts[:prefix] = @opts[:prefix] + opts[:prefix]
|
134
|
+
if block_given?
|
135
|
+
yield self.class.new(res, @procs, opts)
|
136
|
+
else
|
137
|
+
@procs << proc { ::Merb::RouteMatcher.generate_singleton_routes(res, opts) }
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
# add a resource to be compiled for rest style dispatch
|
143
|
+
def self.resources(res, opts={})
|
144
|
+
opts[:prefix] ||= ""
|
145
|
+
if block_given?
|
146
|
+
procs = []
|
147
|
+
yield Resource.new(res, procs, opts)
|
148
|
+
procs.reverse.each {|p| p.call}
|
149
|
+
else
|
150
|
+
generate_resources_routes(res,opts)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
# add a resource to be compiled for rest style dispatch
|
155
|
+
def self.resource(res, opts={})
|
156
|
+
opts[:prefix] ||= ""
|
157
|
+
if block_given?
|
158
|
+
procs = []
|
159
|
+
yield Resource.new(res, procs, opts)
|
160
|
+
procs.reverse.each {|p| p.call}
|
161
|
+
else
|
162
|
+
generate_singleton_routes(res,opts)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
def self.generate_resources_routes(res,opt)
|
167
|
+
with_options :controller => res.to_s, :rest => true do |r|
|
168
|
+
r.add "#{opt[:prefix]}/#{res}/:id[;]edit", :allowed => {:get => 'edit'}
|
169
|
+
r.add "#{opt[:prefix]}/#{res}/new[;]:action", :allowed => {:get => 'new', :post => 'new', :put => 'new', :delete => 'new'}
|
170
|
+
r.add "#{opt[:prefix]}/#{res}/new" , :allowed => {:get => 'new'}
|
171
|
+
if mem = opt[:member]
|
172
|
+
mem.keys.sort_by{|x| "#{x}"}.each {|action|
|
173
|
+
allowed = mem[action].inject({}) {|h, verb| h[verb] = "#{action}" ; h}
|
174
|
+
r.add "#{opt[:prefix]}/#{res}/:id[;]#{action}", :allowed => allowed
|
175
|
+
}
|
176
|
+
end
|
177
|
+
if coll = opt[:collection]
|
178
|
+
coll.keys.sort_by{|x| "#{x}"}.each {|action|
|
179
|
+
allowed = coll[action].inject({}) {|h, verb| h[verb] = "#{action}" ; h}
|
180
|
+
r.add "#{opt[:prefix]}/#{res}[;]#{action}", :allowed => allowed
|
181
|
+
}
|
182
|
+
end
|
183
|
+
r.add "#{opt[:prefix]}/#{res}/:id\\.:format", :allowed => {:get => 'show', :put => 'update', :delete => 'destroy'}
|
184
|
+
r.add "#{opt[:prefix]}/#{res}\\.:format", :allowed => {:get => 'index', :post => 'create'}
|
185
|
+
r.add "#{opt[:prefix]}/#{res}/:id", :allowed => {:get => 'show', :put => 'update', :delete => 'destroy'}
|
186
|
+
r.add "#{opt[:prefix]}/#{res}/?", :allowed => {:get => 'index', :post => 'create'}
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
def self.generate_singleton_routes(res,opt)
|
191
|
+
with_options :controller => res.to_s, :rest => true do |r|
|
192
|
+
r.add "#{opt[:prefix]}/#{res}[;]edit", :allowed => {:get => 'edit'}
|
193
|
+
r.add "#{opt[:prefix]}/#{res}\\.:format", :allowed => {:get => 'show'}
|
194
|
+
r.add "#{opt[:prefix]}/#{res}/new" , :allowed => {:get => 'new'}
|
195
|
+
r.add "#{opt[:prefix]}/#{res}/?", :allowed => {:get => 'show', :post => 'create', :put => 'update', :delete => 'destroy'}
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
94
199
|
end
|
95
200
|
|
96
|
-
end
|
201
|
+
end
|
data/lib/merb/merb_server.rb
CHANGED
@@ -14,8 +14,8 @@ module Merb
|
|
14
14
|
:port => "4000",
|
15
15
|
:allow_reloading => true,
|
16
16
|
:merb_root => Dir.pwd,
|
17
|
-
:
|
18
|
-
:
|
17
|
+
:cache_templates => false,
|
18
|
+
:use_mutex => true
|
19
19
|
}
|
20
20
|
begin
|
21
21
|
options = defaults.merge(YAML.load(Erubis::Eruby.new(IO.read("#{defaults[:merb_root]}/dist/conf/merb.yml")).result))
|
@@ -35,12 +35,20 @@ module Merb
|
|
35
35
|
options = Merb::Config.setup
|
36
36
|
|
37
37
|
opts = OptionParser.new do |opts|
|
38
|
-
opts.banner = "Usage: merb [
|
38
|
+
opts.banner = "Usage: merb [fdcepghmisluMG] [argument]"
|
39
39
|
opts.define_head "Merb Mongrel+ Erb. Lightweight replacement for ActionPack"
|
40
40
|
opts.separator '*'*80
|
41
41
|
opts.separator 'If no flags are given, Merb starts in the foreground on port 4000'
|
42
42
|
opts.separator '*'*80
|
43
|
-
|
43
|
+
|
44
|
+
opts.on("-u", "--user USER", "This flag is for having merb run as a user other than the one currently logged in. Note: if you set this you must also provide a --group option for it to take effect.") do |config|
|
45
|
+
options[:user] = config
|
46
|
+
end
|
47
|
+
|
48
|
+
opts.on("-G", "--group GROUP", "This flag is for having merb run as a group other than the one currently logged in. Note: if you set this you must also provide a --user option for it to take effect.") do |config|
|
49
|
+
options[:group] = config
|
50
|
+
end
|
51
|
+
|
44
52
|
opts.on("-f", "--config-file FILENAME", "This flag is for adding extra config files for things like the upload progress module") do |config|
|
45
53
|
options[:config] = config
|
46
54
|
end
|
@@ -69,7 +77,9 @@ module Merb
|
|
69
77
|
options[:console] = true
|
70
78
|
end
|
71
79
|
|
72
|
-
opts.on("-s", "--drb
|
80
|
+
opts.on("-s", "--start-drb PORTNUM", "This is the port number to run the drb daemon on for sessions and uplod progress monitoring.") do |drb_port|
|
81
|
+
options[:start_drb] = true
|
82
|
+
options[:only_drb] = true
|
73
83
|
options[:drb_server_port] = drb_port
|
74
84
|
end
|
75
85
|
|
@@ -85,6 +95,22 @@ module Merb
|
|
85
95
|
options[:generate] = path || Dir.pwd
|
86
96
|
end
|
87
97
|
|
98
|
+
opts.on("-k", "--kill PORT or all", "Kill one merb proceses by port number. use merb -k all to kill all merbs") do |ports|
|
99
|
+
options[:kill] = ports
|
100
|
+
end
|
101
|
+
|
102
|
+
opts.on("-M", "--merb-config FILENAME", "This flag is for explicitly declaring the merb app's config file") do |config|
|
103
|
+
options[:merb_config] = config
|
104
|
+
end
|
105
|
+
|
106
|
+
opts.on("-X", "--mutex on/off", "This flag is for turnhing on and off the mutex lock.") do |mutex|
|
107
|
+
if mutex == 'off'
|
108
|
+
options[:use_mutex] = false
|
109
|
+
else
|
110
|
+
options[:use_mutex] = true
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
88
114
|
opts.on("-?", "--help", "Show this help message") do
|
89
115
|
puts opts
|
90
116
|
exit
|
@@ -94,12 +120,17 @@ module Merb
|
|
94
120
|
|
95
121
|
opts.parse!(@@merb_raw_opts)
|
96
122
|
|
123
|
+
# Added by: Rogelio J. Samour 2007-01-23
|
124
|
+
# We need to reload the options that exist in the App's merb.yml
|
125
|
+
# This is needed when one calls merb NOT from the merb_app ROOT
|
126
|
+
# like so: merb --merb-config /path/to/dist/conf/merb.yml -m /path/to/merb/app
|
127
|
+
# or if we add :merb_root: /path/to/merb/app in the merb.yml we can now only call it
|
128
|
+
# like so: merb --merb-config /path/to/dist/conf/merb.yml
|
129
|
+
if options[:merb_config]
|
130
|
+
options = options.merge(YAML.load(Erubis::Eruby.new(IO.read("#{options[:merb_config]}")).result))
|
131
|
+
end
|
97
132
|
|
98
133
|
@@merb_opts = options
|
99
|
-
unless options[:generate] || options[:console]
|
100
|
-
puts %{Merb started with these options:}
|
101
|
-
puts @@merb_opts.to_yaml; puts
|
102
|
-
end
|
103
134
|
end
|
104
135
|
|
105
136
|
def initialize_merb
|
@@ -112,6 +143,22 @@ module Merb
|
|
112
143
|
@@merb_raw_opts = ARGV
|
113
144
|
merb_config
|
114
145
|
|
146
|
+
if k = @@merb_opts[:kill]
|
147
|
+
begin
|
148
|
+
Dir[@@merb_opts[:merb_root] + "/log/merb.#{k == 'all' ? '*' : k }.pid"].each do |f|
|
149
|
+
puts f
|
150
|
+
pid = IO.read(f).chomp.to_i
|
151
|
+
Process.kill(9, pid)
|
152
|
+
FileUtils.rm f
|
153
|
+
puts "killed PID: #{pid}"
|
154
|
+
end
|
155
|
+
rescue
|
156
|
+
puts "Failed to kill! #{k}"
|
157
|
+
ensure
|
158
|
+
exit
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
115
162
|
case @@merb_opts[:environment].to_s
|
116
163
|
when 'development'
|
117
164
|
@@merb_opts[:allow_reloading] ||= true
|
@@ -157,9 +204,10 @@ module Merb
|
|
157
204
|
exit!
|
158
205
|
end
|
159
206
|
|
160
|
-
if @@merb_opts[:
|
207
|
+
if @@merb_opts[:start_drb]
|
161
208
|
puts "Starting merb drb server on port: #{@@merb_opts[:drb_server_port]}"
|
162
209
|
start(@@merb_opts[:drb_server_port], :drbserver_start)
|
210
|
+
exit if @@merb_opts[:only_drb]
|
163
211
|
end
|
164
212
|
|
165
213
|
if @@merb_opts[:cluster]
|
@@ -211,15 +259,26 @@ module Merb
|
|
211
259
|
def drbserver_start(port)
|
212
260
|
puts "Starting merb drb server on port: #{port}"
|
213
261
|
require 'merb/merb_drb_server'
|
262
|
+
drb_init = File.join(@@merb_opts[:merb_root], "/dist/conf/merb_drb_init")
|
263
|
+
require drb_init if File.exist?(drb_init)
|
214
264
|
DRb.start_service("druby://#{@@merb_opts[:host]}:#{port}", Merb::DrbServiceProvider)
|
215
265
|
DRb.thread.join
|
216
266
|
end
|
217
267
|
|
218
268
|
def mongrel_start(port)
|
219
269
|
@@merb_opts[:port] = port
|
270
|
+
unless @@merb_opts[:generate] || @@merb_opts[:console] || @@merb_opts[:only_drb] || @@merb_opts[:kill]
|
271
|
+
puts %{Merb started with these options:}
|
272
|
+
puts @@merb_opts.to_yaml; puts
|
273
|
+
end
|
220
274
|
initialize_merb
|
221
275
|
|
222
|
-
|
276
|
+
mconf_hash = {:host => (@@merb_opts[:host]||"0.0.0.0"), :port => (port ||4000)}
|
277
|
+
if @@merb_opts[:user] and @@merb_opts[:group]
|
278
|
+
mconf_hash[:user] = @@merb_opts[:user]
|
279
|
+
mconf_hash[:group] = @@merb_opts[:group]
|
280
|
+
end
|
281
|
+
mconfig = Mongrel::Configurator.new(mconf_hash) do
|
223
282
|
yconfig = YAML.load(Erubis::Eruby.new(IO.read(File.expand_path(@@merb_opts[:config]))).result) if @@merb_opts[:config]
|
224
283
|
listener do
|
225
284
|
uri( "/", :handler => MerbUploadHandler.new(yconfig), :in_front => true) if @@merb_opts[:config]
|
@@ -241,4 +300,4 @@ module Merb
|
|
241
300
|
|
242
301
|
end # Server
|
243
302
|
|
244
|
-
end # Merb
|
303
|
+
end # Merb
|
@@ -52,18 +52,14 @@ class MerbUploadHandler < Mongrel::HttpHandler
|
|
52
52
|
end
|
53
53
|
|
54
54
|
def request_aborted(params)
|
55
|
-
return unless
|
56
|
-
params[Mongrel::Const::REQUEST_METHOD] == Mongrel::Const::POST &&
|
57
|
-
upload_id = Mongrel::HttpRequest.query_parse(params[Mongrel::Const::QUERY_STRING])[Mongrel::Const::UPLOAD_ID]
|
55
|
+
return unless upload_id = valid_upload?(params)
|
58
56
|
Mongrel::Uploads.finish(upload_id)
|
59
57
|
puts "request aborted!"
|
60
58
|
end
|
61
59
|
|
62
60
|
private
|
63
61
|
def upload_notify(action, params, *args)
|
64
|
-
return unless
|
65
|
-
params[Mongrel::Const::REQUEST_METHOD] == 'POST' &&
|
66
|
-
upload_id = Mongrel::HttpRequest.query_parse(params['QUERY_STRING'])['upload_id']
|
62
|
+
return unless upload_id = valid_upload?(params)
|
67
63
|
if action == :mark
|
68
64
|
last_checked_time = Mongrel::Uploads.last_checked(upload_id)
|
69
65
|
return unless last_checked_time && Time.now - last_checked_time > @frequency
|
@@ -71,5 +67,11 @@ class MerbUploadHandler < Mongrel::HttpHandler
|
|
71
67
|
Mongrel::Uploads.send(action, upload_id, *args)
|
72
68
|
Mongrel::Uploads.update_checked_time(upload_id) unless action == :finish
|
73
69
|
end
|
70
|
+
|
71
|
+
def valid_upload?(params)
|
72
|
+
@path_info.any? { |p| params[Mongrel::Const::PATH_INFO].include?(p) } &&
|
73
|
+
params[Mongrel::Const::REQUEST_METHOD] == Mongrel::Const::POST &&
|
74
|
+
Mongrel::HttpRequest.query_parse(params[Mongrel::Const::QUERY_STRING])[Mongrel::Const::UPLOAD_ID]
|
75
|
+
end
|
74
76
|
end
|
75
77
|
|
@@ -14,7 +14,6 @@ module Merb
|
|
14
14
|
# and then use as the context object passed to Erubis
|
15
15
|
# when evaluating the templates.
|
16
16
|
class ViewContext
|
17
|
-
include Merb::ErubisCaptureMixin
|
18
17
|
include Merb::ViewContextMixin
|
19
18
|
include Merb::FormControls
|
20
19
|
include Merb::GlobalHelper
|
@@ -31,10 +30,21 @@ module Merb
|
|
31
30
|
end
|
32
31
|
end
|
33
32
|
|
33
|
+
# hack so markaby doesn't dup us and lose ivars.
|
34
|
+
def dup
|
35
|
+
self
|
36
|
+
end
|
37
|
+
|
34
38
|
# accessor for the view. refers to the current @controller object
|
35
39
|
def controller
|
36
40
|
@controller
|
37
|
-
end
|
41
|
+
end
|
42
|
+
|
43
|
+
alias_method :old_respond_to?, :respond_to?
|
44
|
+
|
45
|
+
def respond_to?(sym, include_private=false)
|
46
|
+
old_respond_to?(sym, include_private) || @controller.respond_to?(sym, include_private)
|
47
|
+
end
|
38
48
|
|
39
49
|
# catch any method calls that the controller responds to
|
40
50
|
# and delegate them back to the controller.
|
@@ -48,4 +58,4 @@ module Merb
|
|
48
58
|
|
49
59
|
end
|
50
60
|
|
51
|
-
end
|
61
|
+
end
|
@@ -1,6 +1,140 @@
|
|
1
1
|
module Merb
|
2
2
|
module ControllerMixin
|
3
|
-
|
3
|
+
NAME_REGEX = /Content-Disposition:.* name="?([^\";]*)"?/ni.freeze
|
4
|
+
CONTENT_TYPE_REGEX = /Content-Type: (.*)\r\n/ni.freeze
|
5
|
+
FILENAME_REGEX = /Content-Disposition:.* filename="?([^\";]*)"?/ni.freeze
|
6
|
+
CRLF = "\r\n".freeze
|
7
|
+
EOL = CRLF
|
8
|
+
def parse_multipart(request,boundary)
|
9
|
+
boundary = "--#{boundary}"
|
10
|
+
paramhsh = MerbHash.new
|
11
|
+
buf = ""
|
12
|
+
content_length = @env['CONTENT_LENGTH'].to_i
|
13
|
+
input = request
|
14
|
+
boundary_size = boundary.size + EOL.size
|
15
|
+
bufsize = 16384
|
16
|
+
content_length -= boundary_size
|
17
|
+
status = input.read(boundary_size)
|
18
|
+
raise EOFError, "bad content body" unless status == boundary + EOL
|
19
|
+
rx = /(?:#{EOL})?#{Regexp.quote(boundary)}(#{EOL}|--)/
|
20
|
+
|
21
|
+
loop {
|
22
|
+
head = nil
|
23
|
+
body = ''
|
24
|
+
filename = content_type = name = nil
|
25
|
+
|
26
|
+
until head && buf =~ rx
|
27
|
+
if !head && i = buf.index("\r\n\r\n")
|
28
|
+
head = buf.slice!(0, i+2) # First \r\n
|
29
|
+
buf.slice!(0, 2) # Second \r\n
|
30
|
+
|
31
|
+
filename = head[FILENAME_REGEX, 1]
|
32
|
+
content_type = head[CONTENT_TYPE_REGEX, 1]
|
33
|
+
name = head[NAME_REGEX, 1]
|
34
|
+
|
35
|
+
if filename
|
36
|
+
body = Tempfile.new(:Merb)
|
37
|
+
body.binmode if defined? body.binmode
|
38
|
+
end
|
39
|
+
next
|
40
|
+
end
|
41
|
+
|
42
|
+
# Save the read body part.
|
43
|
+
if head && (boundary_size+4 < buf.size)
|
44
|
+
body << buf.slice!(0, buf.size - (boundary_size+4))
|
45
|
+
end
|
46
|
+
|
47
|
+
c = input.read(bufsize < content_length ? bufsize : content_length)
|
48
|
+
raise EOFError, "bad content body" if c.nil? || c.empty?
|
49
|
+
buf << c
|
50
|
+
content_length -= c.size
|
51
|
+
end
|
52
|
+
|
53
|
+
# Save the rest.
|
54
|
+
if i = buf.index(rx)
|
55
|
+
body << buf.slice!(0, i)
|
56
|
+
buf.slice!(0, boundary_size+2)
|
57
|
+
|
58
|
+
content_length = -1 if $1 == "--"
|
59
|
+
end
|
60
|
+
|
61
|
+
if filename
|
62
|
+
body.rewind
|
63
|
+
data = {:filename => filename, :type => content_type, :tempfile => body}
|
64
|
+
else
|
65
|
+
data = body
|
66
|
+
end
|
67
|
+
paramhsh = normalize_params(paramhsh,name,data)
|
68
|
+
break if buf.empty? || content_length == -1
|
69
|
+
}
|
70
|
+
paramhsh
|
71
|
+
end
|
72
|
+
|
73
|
+
def normalize_params(parms, key, val)
|
74
|
+
case key
|
75
|
+
when /(.+)\[(.+)\]\[\]$/
|
76
|
+
parms[$1] ||= MerbHash.new
|
77
|
+
parms[$1] = normalize_params(parms[$1], "#{$2}[]", val)
|
78
|
+
when /(.+)\[(.+)\]$/
|
79
|
+
parms[$1] ||= MerbHash.new
|
80
|
+
parms[$1] = normalize_params(parms[$1], $2, val)
|
81
|
+
when /(.+)\[\]$/
|
82
|
+
(parms[$1] ||= []) << val
|
83
|
+
else
|
84
|
+
parms[key] = val if val
|
85
|
+
end
|
86
|
+
parms
|
87
|
+
end
|
88
|
+
|
89
|
+
# parses a query string or the payload of a POST
|
90
|
+
# request into the params hash. So for example:
|
91
|
+
# /foo?bar=nik&post[title]=heya&post[body]=whatever
|
92
|
+
# parses into:
|
93
|
+
# {:bar => 'nik', :post => {:title => 'heya', :body => 'whatever}}
|
94
|
+
def query_parse(qs, d = '&;')
|
95
|
+
m = proc {|_,o,n|o.u(n,&m)rescue([*o]<<n)}
|
96
|
+
(qs||'').split(/[#{d}] */n).inject(MerbHash[]) { |h,p|
|
97
|
+
k, v=unescape(p).split('=',2)
|
98
|
+
h.u(k.split(/[\]\[]+/).reverse.
|
99
|
+
inject(v) { |x,i| MerbHash[i,x] },&m)
|
100
|
+
}
|
101
|
+
end
|
102
|
+
|
103
|
+
# render using chunked encoding
|
104
|
+
# def stream
|
105
|
+
# prefix = '<p>'
|
106
|
+
# suffix = "</p>\r\n"
|
107
|
+
# render_chunked do
|
108
|
+
# IO.popen("cat /tmp/test.log") do |io|
|
109
|
+
# done = false
|
110
|
+
# until done == true do
|
111
|
+
# sleep 0.3
|
112
|
+
# line = io.gets.chomp
|
113
|
+
# if line == 'EOF'
|
114
|
+
# done = true
|
115
|
+
# else
|
116
|
+
# send_chunk(prefix + line + suffix)
|
117
|
+
# end
|
118
|
+
# end
|
119
|
+
# end
|
120
|
+
# end
|
121
|
+
# end
|
122
|
+
def render_chunked(&blk)
|
123
|
+
headers['Transfer-Encoding'] = 'chunked'
|
124
|
+
Proc.new {
|
125
|
+
response.send_status_no_connection_close(0)
|
126
|
+
response.send_header
|
127
|
+
blk.call
|
128
|
+
response.write("0\r\n\r\n")
|
129
|
+
}
|
130
|
+
end
|
131
|
+
|
132
|
+
# for use within a render_chunked response
|
133
|
+
def send_chunk(data)
|
134
|
+
response.write('%x' % data.size + "\r\n")
|
135
|
+
response.write(data + "\r\n")
|
136
|
+
end
|
137
|
+
|
4
138
|
# redirect to another url It can be like /foo/bar
|
5
139
|
# for redirecting within your same app. Or it can
|
6
140
|
# be a fully qualified url to another site.
|
@@ -31,7 +165,7 @@ module Merb
|
|
31
165
|
# a file directly from nginx. See the nginx wiki:
|
32
166
|
# http://wiki.codemongers.com/NginxXSendfile
|
33
167
|
def nginx_send_file(file)
|
34
|
-
headers['X-Accel-Redirect'] = file
|
168
|
+
headers['X-Accel-Redirect'] = File.expand_path(file)
|
35
169
|
return
|
36
170
|
end
|
37
171
|
|
@@ -47,26 +181,24 @@ module Merb
|
|
47
181
|
set_cookie(name, nil, Merb::Const::COOKIE_EXPIRED_TIME)
|
48
182
|
end
|
49
183
|
|
50
|
-
# parses a query string or the payload of a POST
|
51
|
-
# request into the params hash. So for example:
|
52
|
-
# /foo?bar=nik&post[title]=heya&post[body]=whatever
|
53
|
-
# parses into:
|
54
|
-
# {:bar => 'nik', :post => {:title => 'heya', :body => 'whatever}}
|
55
|
-
def query_parse(qs, d = '&;')
|
56
|
-
m = proc {|_,o,n|o.u(n,&m)rescue([*o]<<n)}
|
57
|
-
(qs||'').split(/[#{d}] */n).inject(MerbHash[]) { |h,p|
|
58
|
-
k, v=unescape(p).split('=',2)
|
59
|
-
h.u(k.split(/[\]\[]+/).reverse.
|
60
|
-
inject(v) { |x,i| MerbHash[i,x] },&m)
|
61
|
-
}
|
62
|
-
end
|
63
|
-
|
64
184
|
# creates a random token like:
|
65
185
|
# "b9a82e011694cc13a4249731b9e83cea"
|
66
186
|
def make_token
|
67
187
|
require 'digest/md5'
|
68
188
|
Digest::MD5.hexdigest("#{inspect}#{Time.now}#{rand}")
|
69
189
|
end
|
190
|
+
|
191
|
+
def rand_uuid
|
192
|
+
"%04x%04x-%04x-%04x-%04x-%06x%06x" % [
|
193
|
+
rand(0x0010000),
|
194
|
+
rand(0x0010000),
|
195
|
+
rand(0x0010000),
|
196
|
+
rand(0x0010000),
|
197
|
+
rand(0x0010000),
|
198
|
+
rand(0x1000000),
|
199
|
+
rand(0x1000000),
|
200
|
+
]
|
201
|
+
end
|
70
202
|
|
71
203
|
def escape_xml(obj)
|
72
204
|
obj.to_s.gsub(/[&<>"']/) { |s| Merb::Const::ESCAPE_TABLE[s] }
|
@@ -85,4 +217,4 @@ module Merb
|
|
85
217
|
end
|
86
218
|
|
87
219
|
end
|
88
|
-
end
|
220
|
+
end
|