kiss 0.9.4 → 1.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.
@@ -2,30 +2,24 @@ class Kiss
2
2
  class Action
3
3
  include Kiss::TemplateMethods
4
4
 
5
- # Class Methods
6
-
7
- class << self
8
- # Sets reference back to controller (Kiss instance).
9
- def set_controller(controller)
10
- @@controller = controller
11
- end
12
- end
13
-
14
- # Instance Methods
15
-
16
5
  # Raises custom error message for missing methods, specifying current action
17
6
  # more clearly than standard error message.
18
7
  def method_missing(meth)
19
- raise NoMethodError, "undefined method `#{meth}' for action `#{@action}'"
8
+ raise NoMethodError, "undefined method `#{meth}'"
9
+ end
10
+
11
+ def controller
12
+ @controller
20
13
  end
21
14
 
22
15
  # Creates a new action instance from controller data.
23
- def initialize
24
- @action = @@controller.action
16
+ def initialize(controller)
17
+ @controller = controller
18
+ @action = @controller.action
25
19
 
26
20
  @data = {}
27
21
  @forms = {}
28
- @template_dir = @@controller.template_dir
22
+ @template_dir = @controller.template_dir
29
23
  end
30
24
 
31
25
  # Invokes controller's file_cache, setting exception class to Kiss::TemplateFileNotFound,
@@ -35,10 +29,6 @@ class Kiss
35
29
  controller.file_cache(path,fnf_file_type,fnf_exception_class,&block)
36
30
  end
37
31
 
38
- def controller
39
- @@controller
40
- end
41
-
42
32
  def forms
43
33
  @forms
44
34
  end
@@ -46,50 +36,39 @@ class Kiss
46
36
  # Returns true is `form' param matches current action path.
47
37
  def form_action_match
48
38
  form = params['form']
49
- return false unless form =~ /^\w+(\/\w+)*$/s
39
+ return false unless form =~ /^\/?\w+(\/\w+)*$/s
50
40
  return form == @action || @action =~ /\/#{form}/
51
41
  end
52
42
 
53
43
  # Returns object from specified table, with id matching first argument (or argument of specified index).
54
- def arg_object(table,index = 0)
55
- result = dbm[table][arg(index)]
56
- raise "#{table.to_s.singularize} not found" unless result
57
-
44
+ def arg_object(table,index = 0,desired_values = {})
45
+ id = arg(index)
46
+ result = dbm[table].where(:id => id).first || (raise "#{table.to_s.singularize} id=#{id} not found")
47
+ desired_values.each_pair do |key,value|
48
+ unless result[key] == value
49
+ raise "#{table.to_s.singularize} id=#{id} does not have #{key} == #{value.inspect}"
50
+ end
51
+ end
58
52
  result
59
53
  end
60
54
 
61
55
  # Returns arg_object, or new object of specified table if arg_object is not found.
62
- def arg_object_or_new(table,*args)
63
- return (arg_object(table,*args) ) rescue dbm[table].new
56
+ def arg_object_or_new(table,index = 0,desired_values = {})
57
+ begin
58
+ arg_object(table,index,desired_values)
59
+ rescue => e
60
+ dbm[table].new(desired_values)
61
+ end
64
62
  end
65
63
 
66
64
  # Validates param of specified key against specified format.
67
65
  def validate_param(key, format, required = false)
68
- value = @params[key]
69
- print_key = key.gsub('_',' ')
70
-
71
- case (Kiss.validate_value(value, format, required))
72
- when :missing:
73
- raise "required param '#{key}' is missing"
74
- when :invalid:
75
- raise "param '#{key}' is invalid"
76
- end
77
-
78
- value
66
+ Kiss.validate_value(@params[key], format, required, "param '#{key}'")
79
67
  end
80
68
 
81
69
  # Validates argument of specified index against specified format.
82
- def validate_arg(index, options)
83
- value = @args[index]
84
-
85
- case (Kiss.validate_value(value, format, required))
86
- when :missing:
87
- raise "required arg #{index} is missing"
88
- when :invalid:
89
- raise "arg #{index} is invalid"
90
- end
91
-
92
- value
70
+ def validate_arg(index, format, required = false)
71
+ Kiss.validate_value(@args[index], format, required, "arg #{index}")
93
72
  end
94
73
 
95
74
  # Creates and invokes new Kiss::Mailer instance to send email message via sendmail.
@@ -120,7 +99,6 @@ class Kiss
120
99
  # Convert specified action path by prefixing current action_subdir,
121
100
  # unless specified path starts with slash (/).
122
101
  def app_absolute_path(path)
123
- debug path
124
102
  path = controller.action_subdir + '/' + path if path !~ /\A\//
125
103
  path
126
104
  end
@@ -132,10 +110,10 @@ class Kiss
132
110
  elsif options[:text]
133
111
  return options[:text].to_s
134
112
  end
135
-
113
+
136
114
  @base_url ||= app(action_subdir + '/')
137
115
  @layout = options.is_a?(Hash) && options.has_key?(:layout) ? options[:layout] :
138
- (extension == 'rhtml' ? "/_layout" : nil)
116
+ (extension == 'rhtml' ? Kiss.layout : nil)
139
117
 
140
118
  content = options[:content].is_a?(String) ? options[:content] : process({
141
119
  :template => action,
@@ -151,7 +129,7 @@ class Kiss
151
129
  # clear layout
152
130
  # (however, layout template is allowed to set @layout to something else)
153
131
  @layout = nil
154
-
132
+
155
133
  # process layout file
156
134
  content = erubis(layout_path,binding,content)
157
135
  end
@@ -177,21 +155,21 @@ class Kiss
177
155
  attrs[:name] ||= @action
178
156
  form_name = attrs[:name]
179
157
  raise "page contains multiple forms named '#{form_name}'" if @forms.has_key?(form_name)
180
-
158
+
181
159
  # create form
182
160
  uri = app + request.path_info
183
161
  @forms[form_name] = @form = Kiss::Form.new({
184
162
  :action => uri,
185
163
  :request => self
186
164
  }.merge(attrs))
187
-
165
+
188
166
  # add data from controller to form
189
167
  if params['form'] == @form.name
190
168
  @form.submitted = true
191
169
  @form.params = params
192
170
  end
193
-
194
- return @form
171
+
172
+ @form
195
173
  end
196
174
 
197
175
  # Returns most recently added form.
@@ -0,0 +1,54 @@
1
+ class Kiss
2
+ # Generates HTML reports on exceptions raised from the app,
3
+ # showing the backtrace with clickable stack frames with
4
+ # TextMate links to source files, plus login hash, last SQL,
5
+ # GET/POST params, cookies, and Rack environment variables.
6
+ module Bench
7
+ def prepend_benchmarks(body)
8
+ html = <<-EOT
9
+ <style>
10
+ .kiss_bench {
11
+ text-align: left;
12
+ padding: 3px 7px;
13
+ border: 1px solid #ec4;
14
+ border-top: 1px solid #fff4bb;
15
+ border-bottom: 1px solid #d91;
16
+ background-color: #ffe590;
17
+ font-size: 12px;
18
+ color: #101;
19
+ }
20
+ .kiss_bench a {
21
+ color: #930;
22
+ text-decoration: none;
23
+ }
24
+ .kiss_bench a:hover {
25
+ color: #930;
26
+ text-decoration: underline;
27
+ }
28
+ </style>
29
+ EOT
30
+ html += @benchmarks.map do |item|
31
+ start_link = context_link(item[:start_context])
32
+ end_link = context_link(item[:end_context])
33
+
34
+ <<-EOT
35
+ <div class="kiss_bench">
36
+ <tt><b>#{item[:label].to_s.gsub(/\</,'&lt;')} duration: #{sprintf("%0.3f",item[:end_time].to_f - item[:start_time].to_f)} s</b></tt>
37
+ <small style="line-height: 105%; display: block; padding-bottom: 3px">kiss bench<br/>started at #{start_link}<br/>ended at #{end_link || 'controller completion'}</small>
38
+ </div>
39
+ EOT
40
+ end.join
41
+
42
+ body.each {|p| html += p }
43
+ html
44
+ end
45
+
46
+ def context_link(context)
47
+ return nil unless context
48
+
49
+ filename, line, method = context.split(/:/)
50
+ textmate_url = "txmt://open?url=file://" + h(Kiss.absolute_path(filename)) + '&amp;line=' + line
51
+ %Q(<a href="#{textmate_url}">#{filename}:#{line}</a> #{method})
52
+ end
53
+ end
54
+ end
@@ -1,13 +1,45 @@
1
1
  class Kiss
2
- module ControllerAccessors
2
+ module KissAccessors
3
3
  def environment
4
- controller.environment
4
+ Kiss.environment
5
+ end
6
+
7
+ def db
8
+ Kiss.db
5
9
  end
10
+ alias_method :database, :db
6
11
 
12
+ def upload_dir
13
+ Kiss.upload_dir
14
+ end
15
+
16
+ def pub(*args)
17
+ Kiss.pub(*args)
18
+ end
19
+ end
20
+
21
+ module ControllerAccessors
22
+ def debug(object)
23
+ controller.debug(object, Kernel.caller[0])
24
+ end
25
+
26
+ def bench(label = nil)
27
+ controller.bench(label, Kernel.caller[0])
28
+ end
29
+
30
+ def dbm
31
+ controller.dbm
32
+ end
33
+ alias_method :models, :dbm
34
+
35
+ def file_cache(*args,&block)
36
+ controller.file_cache(*args,&block)
37
+ end
38
+
7
39
  def params
8
40
  controller.params
9
41
  end
10
-
42
+
11
43
  def args
12
44
  controller.args
13
45
  end
@@ -24,10 +56,6 @@ class Kiss
24
56
  controller.request
25
57
  end
26
58
 
27
- def pub(*args)
28
- controller.pub(*args)
29
- end
30
-
31
59
  def set_login_expires(*args)
32
60
  controller.set_login_expires(*args)
33
61
  end
@@ -52,20 +80,10 @@ class Kiss
52
80
  controller.login_session_valid?
53
81
  end
54
82
 
55
- def db
56
- controller.db
57
- end
58
- alias_method :database, :db
59
-
60
- def dbm
61
- controller.dbm
62
- end
63
- alias_method :models, :dbm
64
-
65
83
  def app(*args)
66
- controller.app(*args)
84
+ controller.app_url(*args)
67
85
  end
68
-
86
+
69
87
  def host
70
88
  controller.host
71
89
  end
@@ -73,17 +91,13 @@ class Kiss
73
91
  def action
74
92
  controller.action
75
93
  end
76
-
77
- def extension
78
- controller.extension
79
- end
80
94
 
81
95
  def action_subdir
82
96
  controller.action_subdir
83
97
  end
84
-
85
- def upload_dir
86
- controller.upload_dir
98
+
99
+ def extension
100
+ controller.extension
87
101
  end
88
102
 
89
103
  def arg(index)
@@ -97,5 +111,7 @@ class Kiss
97
111
  def new_email
98
112
  controller.new_email
99
113
  end
114
+
115
+ include KissAccessors
100
116
  end
101
117
  end
@@ -0,0 +1,45 @@
1
+ class Kiss
2
+ # Generates HTML reports on exceptions raised from the app,
3
+ # showing the backtrace with clickable stack frames with
4
+ # TextMate links to source files, plus login hash, last SQL,
5
+ # GET/POST params, cookies, and Rack environment variables.
6
+ module Debug
7
+ def prepend_debug(body)
8
+ html = <<-EOT
9
+ <style>
10
+ .kiss_debug {
11
+ text-align: left;
12
+ padding: 3px 7px;
13
+ border: 1px solid #ebe;
14
+ border-top: 1px solid #fdf;
15
+ border-bottom: 1px solid #d6d;
16
+ background-color: #fbf;
17
+ font-size: 12px;
18
+ color: #101;
19
+ }
20
+ .kiss_debug a {
21
+ color: #707;
22
+ text-decoration: none;
23
+ }
24
+ .kiss_debug a:hover {
25
+ color: #707;
26
+ text-decoration: underline;
27
+ }
28
+ </style>
29
+ EOT
30
+ html += @debug_messages.map do |object,context|
31
+ filename, line, method = context.split(/:/)
32
+ textmate_url = "txmt://open?url=file://" + h(Kiss.absolute_path(filename)) + '&amp;line=' + line
33
+ <<-EOT
34
+ <div class="kiss_debug">
35
+ <tt><b>#{object.gsub(/\</,'&lt;')}</b></tt>
36
+ <br><small>kiss debug output at <a href="#{textmate_url}">#{filename}:#{line}</a> #{method}</small>
37
+ </div>
38
+ EOT
39
+ end.join
40
+
41
+ body.each {|p| html += p }
42
+ html
43
+ end
44
+ end
45
+ end
@@ -1,14 +1,18 @@
1
1
  class Kiss
2
- # Generates HTML exception reports for Rack::ShowExceptions and
3
- # Rack::LogExceptions.
2
+ # Generates HTML reports on exceptions raised from the app,
3
+ # showing the backtrace with clickable stack frames with
4
+ # TextMate links to source files, plus login hash, last SQL,
5
+ # GET/POST params, cookies, and Rack environment variables.
4
6
  class ExceptionReport
5
7
  @@context = 7
6
8
 
7
9
  class << self
8
- def generate(env, exception)
9
- req = Rack::Request.new(env)
10
- path = (req.script_name + req.path_info).squeeze("/")
11
- url = req.scheme + '://' + req.host + path
10
+ def generate(exception, env = nil, cache = nil, sql = nil)
11
+ if env
12
+ req = Rack::Request.new(env)
13
+ path = (req.script_name + req.path_info).squeeze("/")
14
+ url = (req.scheme + '://' + req.host + path) rescue 'n/a'
15
+ end
12
16
 
13
17
  backtrace = exception.backtrace
14
18
  #backtrace.shift while lines[0] =~ /\/lib\/kiss(\/|\.rb)/
@@ -38,9 +42,11 @@ class Kiss
38
42
  end
39
43
  }.compact
40
44
 
41
- env["rack.errors"].puts "#{exception.class}: #{exception.message}"
42
- env["rack.errors"].puts exception.backtrace.map { |l| "\t" + l }
43
- env["rack.errors"].flush
45
+ if env
46
+ env["rack.errors"].puts "#{exception.class}: #{exception.message}"
47
+ env["rack.errors"].puts exception.backtrace.map { |l| "\t" + l }
48
+ env["rack.errors"].flush
49
+ end
44
50
 
45
51
  db_query = begin (Sequel::MySQL::Database.last_query) rescue nil end
46
52
 
@@ -48,25 +54,19 @@ class Kiss
48
54
  @@erubis.result(binding)
49
55
  end
50
56
 
51
- def absolute_path(filename)
52
- filename = ( filename =~ /\A\// ? '' : (Dir.pwd + '/') ) + filename
53
- end
54
-
55
57
  def textmate_href(frame)
56
- "txmt://open?url=file://"+(h(absolute_path(frame.filename)))+"&amp;line="+(h frame.lineno)
58
+ "txmt://open?url=file://"+(h(Kiss.absolute_path(frame.filename)))+"&amp;line="+(h frame.lineno)
57
59
  end
58
-
59
- def h(obj) # :nodoc:
60
- case obj
61
- when String
62
- Rack::Utils.escape_html(obj).gsub(/^(\s+)/) {'&nbsp;' * $1.length}
63
- else
64
- Rack::Utils.escape_html(obj.inspect)
65
- end
60
+
61
+ def h(*args)
62
+ Kiss.h(*args)
66
63
  end
67
64
 
68
65
  # :stopdoc:
69
66
 
67
+ # adapted from Rack::ShowExceptions
68
+ # Used under the MIT license
69
+ #
70
70
  # adapted from Django <djangoproject.com>
71
71
  # Copyright (c) 2005, the Lawrence Journal-World
72
72
  # Used under the modified BSD license:
@@ -86,7 +86,7 @@ class Kiss
86
86
  body { font:small sans-serif; }
87
87
  body>div { border-bottom:1px solid #ddd; }
88
88
  h1 { font-weight:normal; font-size: 18px; font-style: italic; color: #d96 }
89
- h2 { margin-bottom:.8em; }
89
+ h2 { font-size: 16px; margin-bottom:.8em; }
90
90
  h2 span { font-size:80%; color:#000; font-weight:normal; }
91
91
  h3 { margin:1em 0 .5em 0; }
92
92
  h4 { margin:0 0 .5em 0; font-weight: normal; }
@@ -117,17 +117,18 @@ class Kiss
117
117
  div.commands a { color:black; text-decoration:none; }
118
118
  #summary { background: #ffd; }
119
119
  #summary h2 { font-weight: normal; color: #a10; font-size: 14px; font-weight: bold }
120
- #summary ul#quicklinks { list-style-type: none; margin-bottom: 2em; }
121
- #summary ul#quicklinks li { float: left; padding: 0 1em; }
122
- #summary ul#quicklinks>li+li { border-left: 1px #666 solid; }
120
+ #summary ul#quicklinks { list-style-type: none; margin: 0 -8px 2em -8px; font-size: 12px }
121
+ #summary ul#quicklinks li { float: left; padding: 0 8px; }
122
+ #summary ul#quicklinks>li+li { border-left: 1px #999 solid; }
123
+ #summary .info th,#summary .info td { padding-top: 6px }
123
124
  #explanation { background:#eee; }
124
125
  #template, #template-not-exist { background:#f6f6f6; }
125
126
  #template-not-exist ul { margin: 0 0 0 20px; }
126
127
  #traceback { background:#eee; }
127
128
  #requestinfo { background:#f6f6f6; padding-left:120px; }
128
129
  #summary table { border:none; background:transparent; }
129
- #requestinfo h2, #requestinfo h3 { position:relative; margin-left:-100px; }
130
- #requestinfo h3 { margin-bottom:-1em; }
130
+ #requestinfo h2, #requestinfo h3 { position:relative; left:-100px; }
131
+ #requestinfo h3 { width:100px; margin-right:-100px; margin-bottom:-1.1em; }
131
132
  .error { background: #ffc; }
132
133
  .specific { color:#cc3300; font-weight:bold; }
133
134
  </style>
@@ -188,23 +189,25 @@ class Kiss
188
189
  <a name="summary"></a>
189
190
  <h1><%=h exception.class %></h1>
190
191
  <h2><code><%=(h exception.message).gsub(/\n/,'<br>') %></code></h2>
191
- <table><tr>
192
+ <% if env %>
193
+ <table class="info"><tr>
192
194
  <th>Ruby</th>
193
195
  <td><code><%=h frames.first.filename %></code>: in <code><%=h frames.first.function %></code>, <a href="<%= textmate_href(frames.first) %>">line <%=h frames.first.lineno %></a></td>
194
196
  </tr><tr>
195
197
  <th>Web</th>
196
198
  <td><code><%=h req.request_method %> <%=h(url)%></code></td>
197
- </tr></table>
198
-
199
- <h3>Jump to:</h3>
200
- <ul id="quicklinks">
201
- <li><a href="#cache">Cache</a></li>
202
- <li><a href="#db-query">DB Query</a></li>
199
+ </tr><tr>
200
+ <th>More</th>
201
+ <td><ul id="quicklinks">
203
202
  <li><a href="#get-info">GET</a></li>
204
203
  <li><a href="#post-info">POST</a></li>
204
+ <li><a href="#cache">Cache</a></li>
205
+ <li><a href="#last-sql">Last SQL</a></li>
205
206
  <li><a href="#cookie-info">Cookies</a></li>
206
207
  <li><a href="#env-info">ENV</a></li>
207
- </ul>
208
+ </ul></td>
209
+ </tr></table>
210
+ <% end %>
208
211
  </div>
209
212
 
210
213
  <div id="traceback">
@@ -241,16 +244,9 @@ class Kiss
241
244
  </ul>
242
245
  </div>
243
246
 
247
+ <% if env %>
244
248
  <div id="requestinfo">
245
249
  <h2>Additional information</h2>
246
-
247
- <a name="cache"></a>
248
- <h3 id="cache">Cache</h3>
249
- <p><code><%=h Kiss.exception_cache.inspect %></code></p>
250
-
251
- <a name="db-query"></a>
252
- <h3 id="db-query">DB Query</h3>
253
- <p><% if db_query %><code><%=(h db_query).gsub(/\n/,'<br/>') %></code><% else %>No database queries executed.<% end %></p>
254
250
 
255
251
  <a name="get-info"></a>
256
252
  <h3 id="get-info">GET</h3>
@@ -297,6 +293,14 @@ class Kiss
297
293
  <% else %>
298
294
  <p>No POST data.</p>
299
295
  <% end %>
296
+
297
+ <a name="cache"></a>
298
+ <h3 id="cache">Cache</h3>
299
+ <p><code><%=h cache.inspect %></code></p>
300
+
301
+ <a name="last-sql"></a>
302
+ <h3 id="last-sql">Last SQL</h3>
303
+ <p><% if sql %><code><%=(h sql).gsub(/\n/,'<br/>') %></code><% else %>n/a<% end %></p>
300
304
 
301
305
  <a name="cookie-info"></a>
302
306
  <h3 id="cookie-info">Cookies</h3>
@@ -341,6 +345,7 @@ class Kiss
341
345
  </table>
342
346
 
343
347
  </div>
348
+ <% end %>
344
349
 
345
350
  <div id="explanation">
346
351
  <p>