kiss 1.1 → 1.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. data/LICENSE +1 -1
  2. data/Rakefile +2 -1
  3. data/VERSION +1 -1
  4. data/bin/kiss +151 -34
  5. data/data/scaffold.tgz +0 -0
  6. data/lib/kiss.rb +389 -742
  7. data/lib/kiss/accessors/controller.rb +47 -0
  8. data/lib/kiss/accessors/request.rb +106 -0
  9. data/lib/kiss/accessors/template.rb +23 -0
  10. data/lib/kiss/action.rb +502 -132
  11. data/lib/kiss/bench.rb +14 -5
  12. data/lib/kiss/debug.rb +14 -6
  13. data/lib/kiss/exception_report.rb +22 -299
  14. data/lib/kiss/ext/core.rb +700 -0
  15. data/lib/kiss/ext/rack.rb +33 -0
  16. data/lib/kiss/ext/sequel_database.rb +47 -0
  17. data/lib/kiss/ext/sequel_mysql_dataset.rb +23 -0
  18. data/lib/kiss/form.rb +404 -179
  19. data/lib/kiss/form/field.rb +183 -307
  20. data/lib/kiss/form/field_types.rb +239 -0
  21. data/lib/kiss/format.rb +88 -70
  22. data/lib/kiss/html/exception_report.css +222 -0
  23. data/lib/kiss/html/exception_report.html +210 -0
  24. data/lib/kiss/iterator.rb +14 -12
  25. data/lib/kiss/login.rb +8 -8
  26. data/lib/kiss/mailer.rb +68 -66
  27. data/lib/kiss/model.rb +323 -36
  28. data/lib/kiss/rack/bench.rb +16 -8
  29. data/lib/kiss/rack/email_errors.rb +25 -15
  30. data/lib/kiss/rack/errors_ok.rb +2 -2
  31. data/lib/kiss/rack/facebook.rb +6 -6
  32. data/lib/kiss/rack/file_not_found.rb +10 -8
  33. data/lib/kiss/rack/log_exceptions.rb +3 -3
  34. data/lib/kiss/rack/recorder.rb +2 -2
  35. data/lib/kiss/rack/show_debug.rb +2 -2
  36. data/lib/kiss/rack/show_exceptions.rb +2 -2
  37. data/lib/kiss/request.rb +435 -0
  38. data/lib/kiss/sequel_session.rb +15 -14
  39. data/lib/kiss/static_file.rb +20 -13
  40. data/lib/kiss/template.rb +327 -0
  41. metadata +60 -25
  42. data/lib/kiss/controller_accessors.rb +0 -81
  43. data/lib/kiss/hacks.rb +0 -188
  44. data/lib/kiss/sequel_mysql.rb +0 -25
  45. data/lib/kiss/template_methods.rb +0 -167
@@ -7,15 +7,15 @@ module Rack
7
7
  # Rack::Bench shows total request duration for any request.
8
8
  class Bench
9
9
  def initialize(app)
10
- @app = app
10
+ @_app = app
11
11
  end
12
12
 
13
13
  def call(env)
14
14
  start_time = Time.now
15
- code, headers, body = @app.call(env)
15
+ code, headers, body = @_app.call(env)
16
16
  end_time = Time.now
17
17
 
18
- contents = <<-EOT
18
+ html = <<-EOT
19
19
  <style>
20
20
  .kiss_bench {
21
21
  text-align: left;
@@ -35,17 +35,25 @@ module Rack
35
35
  color: #930;
36
36
  text-decoration: underline;
37
37
  }
38
+ .kiss_bench small {
39
+ font-family: arial, sans-serif;
40
+ float: right;
41
+ margin-left: 8px;
42
+ color: #a60;
43
+ text-align: right;
44
+ white-space: nowrap;
45
+ }
38
46
  </style>
39
47
  <div class="kiss_bench">
40
- <tt><b>TOTAL request duration: #{sprintf("%0.3f",end_time.to_f - start_time.to_f)} s</b></tt>
41
- <br><small>kiss bench request total</small>
48
+ <small>kiss bench</small>
49
+ <tt><b>TOTAL request duration: #{sprintf("%0.3f", end_time.to_f - start_time.to_f)} s</b></tt>
42
50
  </div>
43
51
  EOT
44
52
 
45
- body.each {|p| contents += p }
46
- headers['Content-Length'] = contents.length.to_s
53
+ body = body.prepend_html(html, 'body')
54
+ headers['Content-Length'] = body.content_length.to_s
47
55
 
48
- [ code, headers, contents ]
56
+ [ code, headers, body ]
49
57
  end
50
58
  end
51
59
  end
@@ -1,17 +1,27 @@
1
+ # DEPRECATED
2
+ # This module is now deprecated as slated to be removed in Kiss 1.6.
3
+
1
4
  module Rack
2
5
  # Rack::EmailErrors sends error responses (code 5xx) to email addresses as
3
6
  # specified in the Rack::Builder config.
4
7
  class EmailErrors
5
- def initialize(app,agent,app_name,from,*to)
6
- @app = app
7
- @agent = agent == :sendmail ? '/usr/sbin/sendmail -t' : agent
8
- @app_name = app_name
9
- @from = from
10
- @to = to.flatten
8
+ def initialize(app, *args)
9
+ @_app = app
10
+
11
+ if (options = args.first).is_a?(Hash)
12
+ @_agent = options[:agent]
13
+ @_subject = options[:subject]
14
+ @_from = options[:from]
15
+ @_to = options[:to]
16
+ else
17
+ @_agent, @_subject, @_from, *@_to = *args
18
+ end
19
+
20
+ @_agent = '/usr/sbin/sendmail -t' if @_agent == :sendmail
11
21
  end
12
22
 
13
23
  def call(env)
14
- code, headers, body = @app.call(env)
24
+ code, headers, body = @_app.call(env)
15
25
 
16
26
  if code >= 500 && code < 600
17
27
  begin # rescue any errors in message composition and sending
@@ -20,9 +30,9 @@ module Rack
20
30
 
21
31
  message = <<-EOT
22
32
  Content-type: text/html
23
- From: #{@from}
24
- To: #{@to.join(', ')}
25
- Subject: #{@app_name} - #{error_type}#{ error_message ? ": #{error_message}" : ''}
33
+ From: #{@_from}
34
+ To: #{@_to.join(', ')}
35
+ Subject: #{@_subject} - #{error_type}#{ error_message ? ": #{error_message}" : ''}
26
36
 
27
37
  EOT
28
38
 
@@ -30,15 +40,15 @@ EOT
30
40
  message += part
31
41
  end
32
42
 
33
- if @agent.is_a?(String)
34
- IO.popen(@agent,"w") do |pipe|
43
+ if @_agent.is_a?(String)
44
+ IO.popen(@_agent, "w") do |pipe|
35
45
  pipe.puts(message)
36
46
  end
37
47
  else
38
48
  require 'net/smtp' unless defined?(Net::SMTP)
39
- smtp = @agent.is_a?(Net::SMTP) ? @agent : Net::SMTP.new('localhost')
49
+ smtp = @_agent.is_a?(Net::SMTP) ? @_agent : Net::SMTP.new('localhost')
40
50
  smtp.start do |smtp|
41
- smtp.send_message(message, @from, *@to)
51
+ smtp.send_message(message, @_from, *@_to)
42
52
  end
43
53
  end
44
54
  rescue
@@ -55,7 +65,7 @@ EOT
55
65
  </body>
56
66
  </html>
57
67
  EOT
58
- headers['Content-Length'] = body.length.to_s
68
+ headers['Content-Length'] = body.content_length.to_s
59
69
  end
60
70
 
61
71
  [ code, headers, body ]
@@ -3,11 +3,11 @@ module Rack
3
3
  # and HTML entities that are invalid as FBML responses.
4
4
  class ErrorsOK
5
5
  def initialize(app)
6
- @app = app
6
+ @_app = app
7
7
  end
8
8
 
9
9
  def call(env)
10
- code, headers, body = @app.call(env)
10
+ code, headers, body = @_app.call(env)
11
11
 
12
12
  if code >= 500 && code < 600
13
13
  code = 200
@@ -3,11 +3,11 @@ module Rack
3
3
  # and HTML entities that are invalid as FBML responses.
4
4
  class Facebook
5
5
  def initialize(app)
6
- @app = app
6
+ @_app = app
7
7
  end
8
8
 
9
9
  def call(env)
10
- code, headers, body = @app.call(env)
10
+ code, headers, body = @_app.call(env)
11
11
 
12
12
  if code >= 500 && code < 600
13
13
  code = 200
@@ -17,11 +17,11 @@ module Rack
17
17
  body.each {|p| contents += p }
18
18
 
19
19
  contents.gsub!(/txmt:\/\//, 'http://textmate.local/')
20
- contents.gsub!('<body>','<div class="body">')
21
- contents.gsub!('</body>','</div>')
22
- contents.gsub!('<wbr/>','')
20
+ contents.gsub!('<body>', '<div class="body">')
21
+ contents.gsub!('</body>', '</div>')
22
+ contents.gsub!('<wbr/>', '')
23
23
 
24
- headers['Content-Length'] = contents.length.to_s
24
+ headers['Content-Length'] = contents.content_length.to_s
25
25
 
26
26
  [ code, headers, contents ]
27
27
  end
@@ -1,11 +1,13 @@
1
1
  module Rack
2
- # Rack::FileNotFound rescues Kiss::TemplateFileNotFound exceptions
2
+ # Rack::FileNotFound rescues file-not-found exceptions
3
3
  # (raised when action template files are not found) and returns an
4
4
  # HTTP 404 error response.
5
5
  class FileNotFound
6
- def initialize(app,path = nil)
7
- @app = app
8
- @body = path ? (
6
+ def initialize(app, options = {})
7
+ @_app = app
8
+
9
+ path = options[:path]
10
+ @_body = path ? (
9
11
  ::File.file?(path) ?
10
12
  ::File.read(path) :
11
13
  template('could not find specified FileNotFound error document')
@@ -13,12 +15,12 @@ module Rack
13
15
  end
14
16
 
15
17
  def call(env)
16
- code, headers, body = @app.call(env)
17
- rescue Kiss::TemplateFileNotFound => e
18
+ code, headers, body = @_app.call(env)
19
+ rescue Kiss::FileNotFoundError => e
18
20
  [ 404, {
19
21
  "Content-Type" => "text/html",
20
- "Content-Length" => @body.length.to_s
21
- }, @body ]
22
+ "Content-Length" => @_body.content_length.to_s
23
+ }, @_body ]
22
24
  end
23
25
 
24
26
  def template(error = nil)
@@ -3,12 +3,12 @@ module Rack
3
3
  # Functionality moved to Kiss#initialize (lib/kiss.rb).
4
4
 
5
5
  class LogExceptions
6
- def initialize(app,path)
7
- @app = app
6
+ def initialize(app, path)
7
+ @_app = app
8
8
  end
9
9
 
10
10
  def call(env)
11
- @app.call(env)
11
+ @_app.call(env)
12
12
  end
13
13
  end
14
14
  end
@@ -1,7 +1,7 @@
1
1
  module Rack
2
2
  class Recorder
3
3
  def initialize(app)
4
- @app = app
4
+ @_app = app
5
5
 
6
6
  @@filepath ||= begin
7
7
  puts "Yo. Starting app..."
@@ -11,7 +11,7 @@ module Rack
11
11
  end
12
12
 
13
13
  def call(env)
14
- code, headers, body = @app.call(env)
14
+ code, headers, body = @_app.call(env)
15
15
 
16
16
  puts "OK, that was fun."
17
17
  puts "Would save some request and response data here."
@@ -4,11 +4,11 @@ module Rack
4
4
 
5
5
  class ShowDebug
6
6
  def initialize(app)
7
- @app = app
7
+ @_app = app
8
8
  end
9
9
 
10
10
  def call(env)
11
- @app.call(env)
11
+ @_app.call(env)
12
12
  end
13
13
  end
14
14
  end
@@ -4,11 +4,11 @@ module Rack
4
4
 
5
5
  class ShowExceptions
6
6
  def initialize(app)
7
- @app = app
7
+ @_app = app
8
8
  end
9
9
 
10
10
  def call(env)
11
- @app.call(env)
11
+ @_app.call(env)
12
12
  end
13
13
  end
14
14
  end
@@ -0,0 +1,435 @@
1
+ class Kiss
2
+ class Request
3
+
4
+ _attr_accessor :controller
5
+
6
+ # Data pertaining to the current request.
7
+ _attr_reader :env, :protocol, :host, :request, :response,
8
+ :exception_cache, :exception_email_sent, :path
9
+
10
+ # Processes and responds to a request.
11
+ # Returns array of response code, headers, and body.
12
+ def initialize(env, controller, passed_config = {})
13
+ @_controller = controller
14
+ @_config = passed_config
15
+
16
+ @_files_cached_this_request = {}
17
+ end
18
+
19
+ def call(env)
20
+ @_env = env
21
+
22
+ if @_controller.rack_file && (
23
+ (env["PATH_INFO"] == '/favicon.ico') ||
24
+ (env["PATH_INFO"].sub!(/\A#{@_controller.asset_uri}/, ''))
25
+ )
26
+ return @_controller.rack_file.call(env)
27
+ elsif env["PATH_INFO"] =~ /favicon\.\w{3}\Z/
28
+ return [404, {'Content-type' => 'text/html'}, 'File not found']
29
+ end
30
+
31
+ @_request = Rack::Request.new(env)
32
+
33
+ @_protocol, @_app_host = (@_request.server.split(/\:\/\//, 2) rescue ['', ''])
34
+ @_app_host = @_config[:app_host] if @_config[:app_host]
35
+ @_app_uri = @_config[:app_uri] || @_request.script_name || ''
36
+
37
+ @_host ||= @_request.host rescue ''
38
+
39
+ # unfreeze path
40
+ @_path = "#{@_request.path_info}" || '/'
41
+
42
+ # remove extra path noise +[..][R]+GET...
43
+ @_path.sub!(/\++(\[.*?\]\+*)*\[R\].*/, '')
44
+
45
+ @_params = @_request.params
46
+ @_query_string = @_request.query_string
47
+
48
+ # catch and report exceptions in this block
49
+ status_code, headers, body = handle_request(@_path, @_params)
50
+
51
+ if body.respond_to?(:prepend_html)
52
+ unless @_debug_messages.empty?
53
+ extend Kiss::Debug
54
+ body = prepend_debug(body)
55
+
56
+ headers['Content-Type'] = 'text/html'
57
+ headers['Content-Length'] = body.content_length.to_s
58
+ end
59
+
60
+ unless @_benchmarks.empty?
61
+ stop_benchmark
62
+ extend Kiss::Bench
63
+ body = prepend_benchmarks(body)
64
+ headers['Content-Type'] = 'text/html'
65
+ headers['Content-Length'] = body.content_length.to_s
66
+ end
67
+ end
68
+
69
+ return_database if @_db
70
+
71
+ [status_code, headers, body]
72
+ end
73
+
74
+ def path_with_query_string
75
+ @_path + (@_query_string.empty? ? '' : '?' + @_query_string)
76
+ end
77
+
78
+ def handle_request(path, params = {})
79
+ begin
80
+ @_exception_cache = {}
81
+ @_debug_messages = []
82
+ @_benchmarks = []
83
+ @_response = Rack::Response.new
84
+
85
+ catch :kiss_request_done do
86
+ action = invoke_action(path, params)
87
+ extension = action.extension
88
+ options = action.output_options
89
+
90
+ if content_type = options[:content_type] || (extension ? Kiss.mime_type(extension) : nil)
91
+ @_response['Content-Type'] = "#{content_type}; #{options[:document_encoding] || 'utf-8'}"
92
+ end
93
+
94
+ send_response(action.output, options)
95
+ end
96
+
97
+ finalize_session if @_session
98
+ @_response.finish
99
+ rescue StandardError, LoadError, SyntaxError => e
100
+ handle_exception(e)
101
+ end
102
+ end
103
+
104
+ def lookup_exception_handler(e)
105
+ klass = e.class
106
+ while klass
107
+ break if controller.exception_handlers[klass]
108
+ klass = klass.superclass
109
+ end
110
+
111
+ klass && controller.exception_handlers[klass]
112
+ end
113
+
114
+ def load_exception_handler(exception_handler); end
115
+
116
+ def handle_exception(e)
117
+ @_exception_messages = []
118
+
119
+ report = Kiss::ExceptionReport.generate(e, @_env, @_exception_cache, @_db ? @_db.last_query : nil)
120
+ exception_message = e.message.sub(/\n.*/m, '')
121
+
122
+ if @_controller.exception_log_file
123
+ @_controller.exception_log_file.print(report + "\n--- End of exception report --- \n\n")
124
+ end
125
+
126
+ status_code = 500
127
+ body = report
128
+
129
+ result = [status_code, {
130
+ "Content-Type" => "text/html",
131
+ "Content-Length" => body.content_length.to_s,
132
+ "X-Kiss-Error-Type" => e.class.name,
133
+ "X-Kiss-Error-Message" => exception_message
134
+ }, body]
135
+
136
+ send_email = true
137
+
138
+ unless @_loading_exception_handler
139
+ @_loading_exception_handler = true
140
+ begin
141
+ exception_handler = lookup_exception_handler(e)
142
+ if exception_handler
143
+ send_email = exception_handler[:send_email]
144
+ new_result = load_exception_handler(exception_handler)
145
+ if exception_handler[:action]
146
+ result = handle_request(exception_handler[:action])
147
+ result[0] = exception_handler[:status_code] || 500
148
+ end
149
+ end
150
+ rescue StandardError, LoadError, SyntaxError => e
151
+ result = handle_exception(e)
152
+ end
153
+ end
154
+
155
+ if send_email && @_config.email_errors
156
+ email_message = <<-EOT
157
+ Content-type: text/html
158
+ From: #{@_config.email_errors.from}
159
+ To: #{@_config.email_errors.to.is_a?(String) ? @_config.email_errors.to : @_config.email_errors.to.join(', ')}
160
+ Subject: #{@_config.email_errors.app} - #{e.class.name}#{ exception_message ? ": #{exception_message}" : ''}
161
+
162
+ #{report}
163
+ EOT
164
+ Kiss::Mailer.send( @_config.email_errors.merge(:message => email_message) )
165
+ @_exception_email_sent = true
166
+ end
167
+
168
+ result
169
+ end
170
+
171
+
172
+ ##### ACTION METHODS #####
173
+
174
+ def invoke_action(path, params = {}, render_options = {})
175
+ action = get_action(path, params)
176
+
177
+ catch :kiss_action_done do
178
+ action.authenticate if action.class.authentication_required
179
+
180
+ action.before_call
181
+ action.call
182
+ action.render(render_options)
183
+ end
184
+ action.after_call
185
+ action
186
+ end
187
+
188
+ # Parses request URI to determine action path and arguments, then
189
+ # instantiates action class to create action handler.
190
+ def get_action(path, params)
191
+ @@action_class ||= Kiss::Action.get_root_class(@_controller, @_controller.action_dir)
192
+
193
+ # return action handler (instance of action class)
194
+ @@action_class.get_subclass_from_path(path, self, params)
195
+ end
196
+
197
+
198
+ ##### FILE METHODS #####
199
+
200
+ # If file has already been cached in handling the current request, retrieve from cache
201
+ # and do not check filesystem for updates. Else cache file via controller's file_cache.
202
+ def file_cache(path, *args, &block)
203
+ return @_controller.file_cache[path] if @_files_cached_this_request[path]
204
+ @_files_cached_this_request[path] = true
205
+
206
+ @_controller.file_cache(path, *args, &block)
207
+ end
208
+
209
+
210
+ ##### DATABASE METHODS #####
211
+
212
+ # Acquires and returns a database connection object from the connection pool.
213
+ #
214
+ # Tip: `db' is a shorthand alias for `database'.
215
+ def database
216
+ @_db ||= begin
217
+ db = @_controller.database
218
+ check_evolution_number(db)
219
+ db.kiss_request = self
220
+ db
221
+ end
222
+ end
223
+ alias_method :db, :database
224
+
225
+ def return_database
226
+ @_db.kiss_request = nil
227
+ @_controller.return_database(@_db)
228
+ end
229
+
230
+ # Kiss Model cache, used to invoke and store Kiss database models.
231
+ #
232
+ # Example:
233
+ # models[:users] == database model for `users' table
234
+ #
235
+ # Tip: `dbm' (stands for `database models') is a shorthand alias for `models'.
236
+ def models
237
+ # make sure we have a database connection
238
+ # create new model cache unless exists already
239
+ db.kiss_model_cache
240
+ end
241
+ alias_method :dbm, :models
242
+
243
+ # Check whether there exists a file in evolution_dir whose number is greater than app's
244
+ # current evolution number. If so, raise an error to indicate need to apply new evolutions.
245
+ def check_evolution_number(db)
246
+ db_version = db.evolution_number
247
+ if @_controller.directory_exists?(@_controller.evolution_dir) && @_controller.evolution_file(db_version+1)
248
+ raise <<-EOT
249
+ database evolution number #{db_version} < last evolution file number #{@_controller.last_evolution_file_number}
250
+ apply evolutions or set database evolution number
251
+ EOT
252
+ end
253
+ end
254
+
255
+
256
+ ##### SESSION METHODS #####
257
+
258
+ # Retrieves or generates session data object, based on session ID from cookie value.
259
+ def session
260
+ @_session ||= begin
261
+ @_controller.session_class ? begin
262
+ @_controller.session_setup ||= begin
263
+ # setup session storage
264
+ @_controller.session_class.setup_storage(self)
265
+ true
266
+ end
267
+
268
+ session = @_controller.session_class.persist(self, @_request.cookies[@_controller.cookie_name])
269
+ @_session_fingerprint = Marshal.dump(session.data).hash
270
+
271
+ cookie_vars = {
272
+ :value => session.values[:session_id],
273
+ :path => @_config[:cookie_path] || @_app_uri,
274
+ :domain => @_config[:cookie_domain] || @_request.host
275
+ }
276
+ cookie_vars[:expires] = Time.now + @_config[:cookie_lifespan] if @_config[:cookie_lifespan]
277
+
278
+ # set_cookie here or at render time
279
+ @_response.set_cookie @_controller.cookie_name, cookie_vars
280
+
281
+ session
282
+ end : {}
283
+ end
284
+ end
285
+
286
+ # Saves session to session store, if session data has changed since load.
287
+ def finalize_session
288
+ @_session.save if @_session_fingerprint != Marshal.dump(@_session.data).hash
289
+ end
290
+
291
+ # Returns a Kiss::Login object containing data from session.login.
292
+ def login
293
+ @_login ||= Kiss::Login.new(session)
294
+ end
295
+
296
+
297
+ ##### OUTPUT METHODS #####
298
+
299
+ # Outputs a Kiss::StaticFile object as response to Rack.
300
+ # Used to return static files efficiently.
301
+ def send_file(path, options = {})
302
+ @_response = Kiss::StaticFile.new(path, options)
303
+
304
+ throw :kiss_request_done
305
+ end
306
+
307
+ # Prepares Rack::Response object to return application response to Rack.
308
+ def send_response(output = '', options = {})
309
+ @_response['Content-Length'] = output.content_length.to_s
310
+ @_response['Content-Type'] = options[:content_type] if options[:content_type]
311
+ if options[:filename]
312
+ @_response['Content-Disposition'] = "#{options[:disposition] || 'inline'}; filename=#{options[:filename]}"
313
+ elsif options[:disposition]
314
+ @_response['Content-Disposition'] = options[:disposition]
315
+ end
316
+ @_response.body = output
317
+
318
+ throw :kiss_request_done
319
+ end
320
+
321
+ # Sends HTTP 302 response to redirect client browser agent to specified URL.
322
+ def redirect_url(url)
323
+ @_response.status = 302
324
+ @_response['Location'] = url
325
+ @_response.body = ''
326
+
327
+ throw :kiss_request_done
328
+ end
329
+
330
+ # Redirects to specified action path, which may also include arguments.
331
+ def redirect_action(action, options = {})
332
+ redirect_url( app_url(options) + action + (options[:params] ?
333
+ '?' + options[:params].keys.map do |k|
334
+ "#{k.to_s.url_escape}=#{options[:params][k].to_s.url_escape}"
335
+ end.join('&') : '')
336
+ )
337
+ end
338
+
339
+
340
+ ##### DEBUG/BENCH OUTPUT #####
341
+
342
+ # Adds debug message to inspect object. Debug messages will be shown at top of
343
+ # application response body.
344
+ def debug(object, context = Kernel.caller[0])
345
+ @_debug_messages.push( [object.inspect.gsub(/ /, ' &nbsp;'), context] )
346
+ object
347
+ end
348
+ alias_method :trace, :debug
349
+
350
+ # Starts a new benchmark timer, with optional label. Benchmark results will be shown
351
+ # at top of application response body.
352
+ def start_benchmark(label = nil, context = Kernel.caller[0])
353
+ stop_benchmark(context)
354
+ @_benchmarks.push(
355
+ :label => label,
356
+ :start_time => Time.now,
357
+ :start_context => context
358
+ )
359
+ end
360
+ alias_method :bench, :start_benchmark
361
+
362
+ # Stops last benchmark timer, if still running.
363
+ def stop_benchmark(end_context = nil)
364
+ if @_benchmarks[-1] && !@_benchmarks[-1][:end_time]
365
+ @_benchmarks[-1][:end_time] = Time.now
366
+ @_benchmarks[-1][:end_context] = end_context
367
+ end
368
+ end
369
+ alias_method :bench_stop, :stop_benchmark
370
+
371
+ ##### OTHER METHODS #####
372
+
373
+ # Returns URL/URI of app root (corresponding to top level of action_dir).
374
+ def app_url(options = {})
375
+ @_controller.app_url({
376
+ :protocol => @_protocol,
377
+ :host => @_app_host,
378
+ :uri => @_app_uri
379
+ }.merge(options))
380
+ end
381
+
382
+ # Returns URL/URI of app's static assets (asset_host or public_uri).
383
+ def pub_url(options = {})
384
+ if options.empty?
385
+ @_pub ||= (@_controller.asset_host ? @_protocol + '://' + @_controller.asset_host : '') +
386
+ (options[:uri] || @_controller.asset_uri || '')
387
+ else
388
+ (options[:protocol] || @_protocol) + '://' + (options[:host] || @_controller.asset_host) +
389
+ (options[:uri] || @_controller.asset_uri || '')
390
+ end
391
+ end
392
+ alias_method :asset_url, :pub_url
393
+
394
+ def query_string_with_params(params = {})
395
+ params = @_request.GET.merge(params)
396
+ params.keys.map do |key|
397
+ "#{key.to_s.url_escape}=#{params[key].to_s.url_escape}"
398
+ end.join('&')
399
+ end
400
+
401
+ def url_with_params(params = {})
402
+ "#{app_url}#{@_path}?#{query_string_with_params(params)}"
403
+ end
404
+
405
+ # Adds data to be displayed in "Cache" section of Kiss exception reports.
406
+ def exception_cache(data = nil)
407
+ @_exception_cache.merge!(data) if data
408
+ @_exception_cache
409
+ end
410
+ alias_method :set_exception_cache, :exception_cache
411
+
412
+ # Returns new Kiss::Mailer object using specified options.
413
+ def new_email(options = {})
414
+ controller.new_email({
415
+ :request => self
416
+ }.merge(options))
417
+ end
418
+
419
+ def send_email(options = {})
420
+ new_email(options).send
421
+ end
422
+
423
+ def cookies
424
+ request.cookies
425
+ end
426
+
427
+ def set_cookie(*args)
428
+ response.set_cookie(*args)
429
+ end
430
+
431
+ def path_info
432
+ @_request.env["PATH_INFO"]
433
+ end
434
+ end
435
+ end