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