kiss 0.9.4 → 1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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>