merb 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. data/README +71 -14
  2. data/Rakefile +20 -31
  3. data/examples/skeleton.tar +0 -0
  4. data/examples/skeleton/dist/schema/migrations/001_add_sessions_table.rb +1 -1
  5. data/lib/merb.rb +3 -2
  6. data/lib/merb/caching.rb +5 -0
  7. data/lib/merb/caching/action_cache.rb +56 -0
  8. data/lib/merb/caching/fragment_cache.rb +38 -0
  9. data/lib/merb/caching/store/file_cache.rb +82 -0
  10. data/lib/merb/caching/store/memory_cache.rb +67 -0
  11. data/lib/merb/core_ext.rb +1 -1
  12. data/lib/merb/core_ext/merb_hash.rb +35 -0
  13. data/lib/merb/core_ext/merb_object.rb +88 -2
  14. data/lib/merb/merb_controller.rb +71 -69
  15. data/lib/merb/merb_dispatcher.rb +72 -0
  16. data/lib/merb/merb_exceptions.rb +6 -1
  17. data/lib/merb/merb_handler.rb +19 -47
  18. data/lib/merb/merb_mailer.rb +1 -1
  19. data/lib/merb/merb_request.rb +11 -3
  20. data/lib/merb/merb_router.rb +113 -8
  21. data/lib/merb/merb_server.rb +71 -12
  22. data/lib/merb/merb_upload_handler.rb +8 -6
  23. data/lib/merb/merb_upload_progress.rb +1 -1
  24. data/lib/merb/merb_view_context.rb +13 -3
  25. data/lib/merb/mixins/basic_authentication_mixin.rb +1 -3
  26. data/lib/merb/mixins/controller_mixin.rb +149 -17
  27. data/lib/merb/mixins/form_control_mixin.rb +1 -0
  28. data/lib/merb/mixins/render_mixin.rb +148 -151
  29. data/lib/merb/mixins/responder_mixin.rb +133 -18
  30. data/lib/merb/mixins/view_context_mixin.rb +24 -0
  31. data/lib/merb/session/merb_ar_session.rb +4 -4
  32. data/lib/merb/session/merb_memory_session.rb +6 -5
  33. data/lib/merb/template.rb +10 -0
  34. data/lib/merb/template/erubis.rb +52 -0
  35. data/lib/merb/template/haml.rb +77 -0
  36. data/lib/merb/template/markaby.rb +48 -0
  37. data/lib/merb/template/xml_builder.rb +34 -0
  38. metadata +19 -17
  39. data/lib/merb/mixins/merb_status_codes.rb +0 -59
@@ -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.fatal "You need to install the mailfactory gem to use Merb::Mailer"
7
+ MERB_LOGGER.warn "You need to install the mailfactory gem to use Merb::Mailer"
8
8
  end
9
9
 
10
10
  module Merb
@@ -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
-
@@ -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
- condition = " when Regexp.new('#{route[0]}')"
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}.merge(@sections)\n"
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
@@ -14,8 +14,8 @@ module Merb
14
14
  :port => "4000",
15
15
  :allow_reloading => true,
16
16
  :merb_root => Dir.pwd,
17
- :template_ext => {:html => :herb, :js => :jerb, :xml => :xerb},
18
- :cache_templates => false
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 [fdcphmisl] [argument]"
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-server-port PORTNUM", "This is the port number to run the drb daemon on for sessions and uplod progress monitoring.") do |drb_port|
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[:drb_server_port]
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
- mconfig = Mongrel::Configurator.new :host => (@@merb_opts[:host]||"0.0.0.0"), :port => (port ||4000) do
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 params[Mongrel::Const::PATH_INFO] == @path_info &&
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 @path_info.include?(params['PATH_INFO']) &&
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
 
@@ -45,4 +45,4 @@ module Merb
45
45
  end
46
46
  end
47
47
 
48
- end
48
+ end
@@ -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
@@ -29,9 +29,7 @@ module Merb
29
29
  @headers['WWW-Authenticate'] = "Basic realm=\"#{Merb::Server.basic_auth[:domain]}\""
30
30
  return 'Unauthorized'
31
31
  end
32
-
33
- end
34
32
 
35
33
  end
36
34
 
37
- end
35
+ 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