kiss 1.1 → 1.7

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