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
@@ -25,28 +25,37 @@ class Kiss
25
25
  color: #930;
26
26
  text-decoration: underline;
27
27
  }
28
+ .kiss_bench small {
29
+ font-family: arial, sans-serif;
30
+ color: #a60;
31
+ float: right;
32
+ margin-left: 8px;
33
+ position: relative;
34
+ text-align: right;
35
+ white-space: nowrap;
36
+ }
28
37
  </style>
29
38
  EOT
30
- html += @benchmarks.map do |item|
39
+ html += @_benchmarks.map do |item|
31
40
  start_link = context_link(item[:start_context])
32
41
  end_link = context_link(item[:end_context])
33
42
 
34
43
  <<-EOT
35
44
  <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>
45
+ <small style="line-height: 105%; display: block; top: -4px">kiss bench started at #{start_link}<br/>ended at #{end_link || 'request completion'}</small>
46
+ <tt><b>#{(item[:label] || 'unlabeled bench').to_s.gsub(/\</, '&lt;')} - duration: #{sprintf("%0.3f", item[:end_time].to_f - item[:start_time].to_f)} s</b></tt>
38
47
  </div>
39
48
  EOT
40
49
  end.join
41
50
 
42
- Kiss.html_prepend(html,document,'body')
51
+ document.prepend_html(html, 'body')
43
52
  end
44
53
 
45
54
  def context_link(context)
46
55
  return nil unless context
47
56
 
48
57
  filename, line, method = context.split(/:/)
49
- textmate_url = "txmt://open?url=file://" + h(Kiss.absolute_path(filename)) + '&amp;line=' + line
58
+ textmate_url = "txmt://open?url=file://" + (Kiss.absolute_path(filename).html_escape) + '&amp;line=' + line
50
59
  %Q(<a href="#{textmate_url}">#{filename}:#{line}</a> #{method})
51
60
  end
52
61
  end
@@ -18,27 +18,35 @@ font-size: 12px;
18
18
  color: #101;
19
19
  }
20
20
  .kiss_debug a {
21
- color: #707;
21
+ color: #606;
22
22
  text-decoration: none;
23
23
  }
24
24
  .kiss_debug a:hover {
25
25
  color: #707;
26
26
  text-decoration: underline;
27
27
  }
28
+ .kiss_debug small {
29
+ font-family: arial, sans-serif;
30
+ color: #949;
31
+ float: right;
32
+ margin-left: 8px;
33
+ text-align: right;
34
+ white-space: nowrap;
35
+ }
28
36
  </style>
29
37
  EOT
30
- html += @debug_messages.map do |object,context|
38
+ html += @_debug_messages.map do |object, context|
31
39
  filename, line, method = context.split(/:/)
32
- textmate_url = "txmt://open?url=file://" + Kiss.url_escape(Kiss.absolute_path(filename)) + '&line=' + line
40
+ textmate_url = "txmt://open?url=file://" + Kiss.absolute_path(filename).url_escape + '&line=' + line
33
41
  <<-EOT
34
42
  <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>
43
+ <small>kiss debug at <a href="#{textmate_url}">#{filename}:#{line}</a> #{method}</small>
44
+ <tt><b>#{object.gsub(/\</, '&lt;')}</b></tt>
37
45
  </div>
38
46
  EOT
39
47
  end.join
40
48
 
41
- Kiss.html_prepend(html,document,'body')
49
+ document.prepend_html(html, 'body')
42
50
  end
43
51
  end
44
52
  end
@@ -15,7 +15,6 @@ class Kiss
15
15
  end
16
16
 
17
17
  backtrace = exception.backtrace
18
- #backtrace.shift while (backtrace[0] =~ /\/lib\/kiss(\/|\.rb)/) || (backtrace[0] =~ /\/lib\/sequel/)
19
18
 
20
19
  frames = backtrace.map { |line|
21
20
  frame = {}
@@ -42,6 +41,13 @@ class Kiss
42
41
  end
43
42
  }.compact
44
43
 
44
+ i = 0
45
+ while true do
46
+ break unless (frames[i].filename =~ /\/lib\/kiss(\/|\.rb)/) || (frames[i].filename =~ /\/lib\/sequel/)
47
+ i += 1
48
+ end
49
+ toggle_frame = frames[i] || frames[0]
50
+
45
51
  if env
46
52
  env["rack.errors"].puts "#{exception.class}: #{exception.message}"
47
53
  env["rack.errors"].puts exception.backtrace.map { |l| "\t" + l }
@@ -51,7 +57,11 @@ class Kiss
51
57
  db_query = begin (Sequel::MySQL::Database.last_query) rescue nil end
52
58
 
53
59
  @@erubis ||= Erubis::Eruby.new(template)
54
- @@erubis.result(binding)
60
+ begin
61
+ @@erubis.result(binding)
62
+ rescue => e
63
+ gdebug e.message
64
+ end
55
65
  end
56
66
 
57
67
  def textmate_href(frame)
@@ -59,308 +69,21 @@ class Kiss
59
69
  end
60
70
 
61
71
  def w(str)
62
- str.gsub(/([;\/])/,'\1<wbr/>')
72
+ str.gsub(/([;\/])/, '\1<wbr/>')
63
73
  end
64
74
 
65
- def h(*args)
66
- Kiss.html_escape(*args)
75
+ def h(str)
76
+ str.html_escape
67
77
  end
68
-
78
+
69
79
  # :stopdoc:
70
-
71
- # adapted from Rack::ShowExceptions
72
- # Used under the MIT license
73
- #
74
- # adapted from Django <djangoproject.com>
75
- # Copyright (c) 2005, the Lawrence Journal-World
76
- # Used under the modified BSD license:
77
- # http://www.xfree86.org/3.3.6/COPYRIGHT2.html#5
80
+
78
81
  def template
79
- <<-EOT
80
- <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
81
- <html lang="en">
82
- <head>
83
- <meta http-equiv="content-type" content="text/html; charset=utf-8" />
84
- <meta name="robots" content="NONE,NOARCHIVE" />
85
- <title>Exception: <%=h exception.class %> - <%=h url %></title>
86
- <style type="text/css">
87
- html * { padding:0; margin:0; }
88
- body *, .body * { padding:10px 20px; }
89
- body * *, .body * * { padding:0; }
90
- body { font:small sans-serif; }
91
- body>div { border-bottom:1px solid #ddd; }
92
- h1 { font-weight:normal; font-size: 18px; font-style: italic; color: #d96 }
93
- h2 { font-size: 16px; margin-bottom:.8em; }
94
- h2 span { font-size:80%; color:#000; font-weight:normal; }
95
- h3 { margin:1em 0 .5em 0; }
96
- h4 { margin:0 0 .5em 0; font-weight: normal; }
97
- small { color #444; }
98
- table {
99
- border:1px solid #ccc; border-collapse: collapse; background:white; }
100
- tbody td, tbody th { vertical-align:top; padding:2px 3px; }
101
- thead th {
102
- padding:1px 6px 1px 3px; background:#fefefe; text-align:left;
103
- font-weight:normal; font-size:11px; border:1px solid #ddd; }
104
- tbody th { text-align:right; color:#666; padding-right:.5em; }
105
- table.vars { margin:5px 0 2px 40px; }
106
- table.vars td, table.req td { font-family:monospace; }
107
- table td.code { width:100%;}
108
- table td.code div { overflow:hidden; }
109
- table.source th { color:#666; }
110
- table.source td {
111
- font-family:monospace; white-space:pre; border-bottom:1px solid #eee; }
112
- ul.traceback { list-style-type:none; }
113
- ul.traceback li.frame { padding: 8px; border-top: 1px solid #ccc; }
114
- div.context { margin: 6px 0 2px 40px; background-color: #fff; }
115
- div.context ol {
116
- padding: 1px; margin-bottom: -1px; }
117
- div.context ol li {
118
- font-family:monospace; color:#666; cursor:pointer; padding: 0 2px; }
119
- div.context ol.context-line li { color:black; background-color:#eca; }
120
- div.context ol.context-line li span { float: right; }
121
- div.commands { margin-left: 40px; }
122
- div.commands a { color:black; text-decoration:none; }
123
- #summary { background: #ffd; }
124
- #summary h2 { font-weight: normal; color: #a10; font-size: 14px; font-weight: bold }
125
- #summary ul#quicklinks { list-style-type: none; margin: 0 -8px 2em -8px; font-size: 12px }
126
- #summary ul#quicklinks li { float: left; padding: 0 8px; }
127
- #summary ul#quicklinks>li+li { border-left: 1px #999 solid; }
128
- #summary .info th,#summary .info td { padding-top: 6px }
129
- #explanation { background:#eee; }
130
- #template, #template-not-exist { background:#f6f6f6; }
131
- #template-not-exist ul { margin: 0 0 0 20px; }
132
- #traceback { background:#eee; }
133
- #requestinfo { background:#f6f6f6; padding-left:120px; }
134
- #summary table { border:none; background:transparent; }
135
- #requestinfo h2, #requestinfo h3 { position:relative; left:-100px; }
136
- #requestinfo h3 { width:100px; margin-right:-100px; margin-bottom:-1.1em; }
137
- .error { background: #ffc; }
138
- .specific { color:#cc3300; font-weight:bold; }
139
- </style>
140
- <script type="text/javascript">
141
- //<!--
142
- function getElementsByClassName(oElm, strTagName, strClassName){
143
- // Written by Jonathan Snook, http://www.snook.ca/jon;
144
- // Add-ons by Robert Nyman, http://www.robertnyman.com
145
- var arrElements = (strTagName == "*" && document.all)? document.all :
146
- oElm.getElementsByTagName(strTagName);
147
- var arrReturnElements = new Array();
148
- strClassName = strClassName.replace(/\-/g, "\\-");
149
- var oRegExp = new RegExp("(^|\\s)" + strClassName + "(\\s|$$)");
150
- var oElement;
151
- for(var i=0; i<arrElements.length; i++){
152
- oElement = arrElements[i];
153
- if(oRegExp.test(oElement.className)){
154
- arrReturnElements.push(oElement);
155
- }
156
- }
157
- return (arrReturnElements)
158
- }
159
- function hideAll(elems) {
160
- for (var e = 0; e < elems.length; e++) {
161
- elems[e].style.display = 'none';
162
- }
163
- }
164
- window.onload = function() {
165
- hideAll(getElementsByClassName(document, 'table', 'vars'));
166
- hideAll(getElementsByClassName(document, 'ol', 'pre-context'));
167
- hideAll(getElementsByClassName(document, 'ol', 'post-context'));
168
- toggle('pre<%=h frames.first.object_id %>', 'post<%=h frames.first.object_id %>')
169
- }
170
- function toggle() {
171
- for (var i = 0; i < arguments.length; i++) {
172
- var e = document.getElementById(arguments[i]);
173
- if (e) {
174
- e.style.display = e.style.display == 'none' ? 'block' : 'none';
175
- }
176
- }
177
- return false;
178
- }
179
-
180
- function varToggle(link, id) {
181
- toggle('v' + id);
182
- var s = link.getElementsByTagName('span')[0];
183
- var uarr = String.fromCharCode(0x25b6);
184
- var darr = String.fromCharCode(0x25bc);
185
- s.innerHTML = s.innerHTML == uarr ? darr : uarr;
186
- return false;
187
- }
188
- //-->
189
- </script>
190
- </head>
191
- <body>
192
-
193
- <div id="summary">
194
- <a name="summary"></a>
195
- <h1><%=h exception.class %></h1>
196
- <h2><code><%=(h exception.message).gsub(/\n/,'<br>') %></code></h2>
197
- <% if env %>
198
- <table class="info"><tr>
199
- <th>Ruby</th>
200
- <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>
201
- </tr><tr>
202
- <th>Web</th>
203
- <td><code><%=h req.request_method %> <%=h(url)%></code></td>
204
- </tr><tr>
205
- <th>More</th>
206
- <td><ul id="quicklinks">
207
- <li><a href="#get-info">GET</a></li>
208
- <li><a href="#post-info">POST</a></li>
209
- <li><a href="#cache">Cache</a></li>
210
- <li><a href="#last-sql">Last SQL</a></li>
211
- <li><a href="#cookie-info">Cookies</a></li>
212
- <li><a href="#env-info">ENV</a></li>
213
- </ul></td>
214
- </tr></table>
215
- <% end %>
216
- </div>
217
-
218
- <div id="traceback">
219
- <h2>Traceback <span>(innermost first)</span></h2>
220
- <ul class="traceback">
221
- <% frames.each { |frame| %>
222
- <li class="frame">
223
- <a href="<%= textmate_href(frame) %>">line <%= h frame.lineno %></a> of <code><%=w h frame.filename %></code> (in <code><b><%=h frame.function %></b></code>)
224
-
225
- <% if frame.context_line %>
226
- <div class="context" id="c<%=h frame.object_id %>">
227
- <% if frame.pre_context %>
228
- <ol start="<%=h frame.pre_context_lineno+1 %>" class="pre-context" id="pre<%=h frame.object_id %>">
229
- <% frame.pre_context.each { |line| %>
230
- <li onclick="toggle('pre<%=h frame.object_id %>', 'post<%=h frame.object_id %>')"><%=h line %></li>
231
- <% } %>
232
- </ol>
233
- <% end %>
234
-
235
- <ol start="<%=h frame.lineno %>" class="context-line">
236
- <li onclick="toggle('pre<%=h frame.object_id %>', 'post<%=h frame.object_id %>')"><%=h frame.context_line %></li></ol>
237
-
238
- <% if frame.post_context %>
239
- <ol start='<%=h frame.lineno+1 %>' class="post-context" id="post<%=h frame.object_id %>">
240
- <% frame.post_context.each { |line| %>
241
- <li onclick="toggle('pre<%=h frame.object_id %>', 'post<%=h frame.object_id %>')"><%=h line %></li>
242
- <% } %>
243
- </ol>
244
- <% end %>
245
- </div>
246
- <% end %>
247
- </li>
248
- <% } %>
249
- </ul>
250
- </div>
251
-
252
- <% if env %>
253
- <div id="requestinfo">
254
- <h2>Additional information</h2>
255
-
256
- <a name="get-info"></a>
257
- <h3 id="get-info">GET</h3>
258
- <% unless req.GET.empty? %>
259
- <table class="req">
260
- <thead>
261
- <tr>
262
- <th>Variable</th>
263
- <th>Value</th>
264
- </tr>
265
- </thead>
266
- <tbody>
267
- <% req.GET.sort_by { |k, v| k.to_s }.each { |key, val| %>
268
- <tr>
269
- <td><%=h key %></td>
270
- <td class="code"><div><%=h val.inspect %></div></td>
271
- </tr>
272
- <% } %>
273
- </tbody>
274
- </table>
275
- <% else %>
276
- <p>No GET data.</p>
277
- <% end %>
278
-
279
- <a name="post-info"></a>
280
- <h3 id="post-info">POST</h3>
281
- <% unless req.POST.empty? %>
282
- <table class="req">
283
- <thead>
284
- <tr>
285
- <th>Variable</th>
286
- <th>Value</th>
287
- </tr>
288
- </thead>
289
- <tbody>
290
- <% req.POST.sort_by { |k, v| k.to_s }.each { |key, val| %>
291
- <tr>
292
- <td><%=h key %></td>
293
- <td class="code"><div><%=h val.inspect %></div></td>
294
- </tr>
295
- <% } %>
296
- </tbody>
297
- </table>
298
- <% else %>
299
- <p>No POST data.</p>
300
- <% end %>
301
-
302
- <a name="cache"></a>
303
- <h3 id="cache">Cache</h3>
304
- <p><code><%=h cache.inspect %></code></p>
305
-
306
- <a name="last-sql"></a>
307
- <h3 id="last-sql">Last SQL</h3>
308
- <p><% if sql %><code><%=(h sql).gsub(/\n/,'<br/>') %></code><% else %>n/a<% end %></p>
309
-
310
- <a name="cookie-info"></a>
311
- <h3 id="cookie-info">Cookies</h3>
312
- <% unless req.cookies.empty? %>
313
- <table class="req">
314
- <thead>
315
- <tr>
316
- <th>Variable</th>
317
- <th>Value</th>
318
- </tr>
319
- </thead>
320
- <tbody>
321
- <% req.cookies.each { |key, val| %>
322
- <tr>
323
- <td><%=h key %></td>
324
- <td class="code"><div><%=h val.inspect %></div></td>
325
- </tr>
326
- <% } %>
327
- </tbody>
328
- </table>
329
- <% else %>
330
- <p>No cookie data.</p>
331
- <% end %>
332
-
333
- <a name="env-info"></a>
334
- <h3 id="env-info">Rack ENV</h3>
335
- <table class="req">
336
- <thead>
337
- <tr>
338
- <th>Variable</th>
339
- <th>Value</th>
340
- </tr>
341
- </thead>
342
- <tbody>
343
- <% env.sort_by { |k, v| k.to_s }.each { |key, val| %>
344
- <tr>
345
- <td><%=h key %></td>
346
- <td class="code"><div><%=w h val %></div></td>
347
- </tr>
348
- <% } %>
349
- </tbody>
350
- </table>
351
-
352
- </div>
353
- <% end %>
354
-
355
- <div id="explanation">
356
- <p>
357
- Generated by Kiss::ExceptionReport.
358
- </p>
359
- </div>
360
-
361
- </body>
362
- </html>
363
- EOT
82
+ @@template ||= File.read( template_dir + '/exception_report.html' )
83
+ end
84
+
85
+ def template_dir
86
+ @@template_dir = File.dirname(__FILE__) + '/html'
364
87
  end
365
88
 
366
89
  # :startdoc:
@@ -0,0 +1,700 @@
1
+ def gdebug(object)
2
+ Kernel::print %Q(Content-type: text/html\n\n<div style="background-color: #fff">) unless $gdebug_filename || $previous_gdebug
3
+ $previous_gdebug = true
4
+
5
+ if $gdebug_filename
6
+ file = File.open($gdebug_filename, 'a')
7
+ file.print "#{object.inspect}\n at #{Kernel.caller[0]}\n\n"
8
+ file.close
9
+ else
10
+ Kernel::print "#{object.inspect}<br/><small>at #{Kernel.caller[0]}</small><br/><hr/>"
11
+ end
12
+
13
+ object
14
+ end
15
+ def gdebug_filename(filename)
16
+ $gdebug_filename = filename
17
+ end
18
+ alias :debug :gdebug
19
+
20
+ def trace(object)
21
+ debug(object)
22
+ end
23
+
24
+
25
+ class Module
26
+ # adapted from Rails, re-written for speed (only one class_eval call)
27
+ def cattr_reader(*syms)
28
+ class_eval(
29
+ syms.flatten.map do |sym|
30
+ sym.is_a?(Hash) ? '' : %Q(
31
+ @@#{sym} = nil unless defined? @@#{sym}
32
+ def self.#{sym}; @@#{sym}; end
33
+ def #{sym}; @@#{sym}; end
34
+ )
35
+ end.join, __FILE__, __LINE__
36
+ )
37
+ end
38
+
39
+ # adapted from Rails, re-written for speed (only one class_eval call)
40
+ def cattr_accessor(*syms)
41
+ class_eval(
42
+ syms.flatten.map do |sym|
43
+ sym.is_a?(Hash) ? '' : %Q(
44
+ @@#{sym} = nil unless defined? @@#{sym}
45
+ def self.#{sym}; @@#{sym}; end
46
+ def self.#{sym}=(v); @@#{sym} = v; end
47
+ def #{sym}; @@#{sym}; end
48
+ def #{sym}=(v); @@#{sym} = v; end
49
+ )
50
+ end.join, __FILE__, __LINE__
51
+ )
52
+ end
53
+
54
+ # protected attribute (ivar) reader
55
+ def _attr_reader(*syms)
56
+ class_eval(
57
+ syms.flatten.map do |sym|
58
+ sym.is_a?(Hash) ? '' : %Q(
59
+ @_#{sym} = nil unless defined? @_#{sym}
60
+ def #{sym}; @_#{sym}; end
61
+ )
62
+ end.join, __FILE__, __LINE__
63
+ )
64
+ end
65
+
66
+ # protected attribute (ivar) accessor
67
+ def _attr_accessor(*syms)
68
+ class_eval(
69
+ syms.flatten.map do |sym|
70
+ sym.is_a?(Hash) ? '' : %Q(
71
+ @_#{sym} = nil unless defined? @_#{sym}
72
+ def #{sym}; @_#{sym}; end
73
+ def #{sym}=(v); @_#{sym} = v; end
74
+ )
75
+ end.join, __FILE__, __LINE__
76
+ )
77
+ end
78
+
79
+ def dsl_accessor(*symbols)
80
+ class_eval(
81
+ symbols.map { |sym|
82
+ %{
83
+ def #{sym}(*val)
84
+ if val.empty?
85
+ @_#{sym}
86
+ else
87
+ @_#{sym} = val.size == 1 ? val[0] : val
88
+ end
89
+ end
90
+ def #{sym}=(val)
91
+ @_#{sym} = val
92
+ end
93
+ }
94
+ }.join)
95
+ end
96
+ end
97
+
98
+ class Object
99
+ def instance_variables_hash
100
+ new Hash[ instance_variables.map {|k| [k, instance_variable_get(k)] }.flatten ]
101
+ end
102
+ def instance_variables_set(h)
103
+ h.each_pair {|k, v| instance_variable_set(k, v)}
104
+ end
105
+
106
+ def _instance_variables_set_from_attrs(attrs)
107
+ attrs.each_pair {|k, v| instance_variable_set(:"@_#{k}", v) }
108
+ end
109
+
110
+ def import_instance_variables(object)
111
+ object.instance_variables.each do |v|
112
+ # skip vars that start with underscore
113
+ unless v[1, 1] == '_'
114
+ instance_variable_set(v, object.instance_variable_get(v))
115
+ end
116
+ end
117
+ end
118
+ def export_instance_variables(object)
119
+ object.import_instance_variables(self)
120
+ end
121
+
122
+ def validate(*args)
123
+ to_s.validate(*args)
124
+ end
125
+ def url_escape(*args)
126
+ to_s.url_escape(*args)
127
+ end
128
+ def html_escape(*args)
129
+ to_s.html_escape(*args)
130
+ end
131
+ end
132
+
133
+ # Methods to enable hash.attr == hash[attr] syntax,
134
+ # including hash.id and hash.type
135
+ class Hash
136
+ def method_missing(meth, *args)
137
+ meth = meth.id2name
138
+ if meth =~ /=\Z/
139
+ self[meth[0...-1]] = (args.length<2 ? args[0] : args)
140
+ elsif meth.to_s =~ /(\w+)\?\Z/
141
+ !(self[$1] || self[$1.to_sym]).zero?
142
+ else
143
+ self[meth] || self[meth.to_sym]
144
+ end
145
+ end
146
+ def id
147
+ self['id'] || self [:id]
148
+ end
149
+ def type
150
+ self['type'] || self [:type] || Hash
151
+ end
152
+
153
+ def prepend_html(*args)
154
+ self[:body].prepend_html(*args)
155
+ end
156
+
157
+ def to_query_string
158
+ self.keys.map {|k| "#{k.url_escape}=#{self[k]}" }.join('&')
159
+ end
160
+ end
161
+
162
+ module Enumerable
163
+ def hash_by_key(key)
164
+ hash = {}
165
+ each do |item|
166
+ hash[item[key]] = item
167
+ end
168
+ hash
169
+ end
170
+
171
+ def hash_by_keys(*keys)
172
+ last_key = keys.pop
173
+ raise 'no keys given' unless last_key
174
+
175
+ hash = {}
176
+ each do |item|
177
+ h = hash
178
+ keys.each do |key|
179
+ h = (h[item[key]] ||= {})
180
+ end
181
+ h[item[last_key]] = item
182
+ end
183
+ hash
184
+ end
185
+
186
+ def hash_by_id
187
+ hash_by_key(:id)
188
+ end
189
+
190
+ def hash_values_by_key(value_key, hash_key)
191
+ hash = {}
192
+ each do |item|
193
+ hash[item[hash_key]] = item[value_key]
194
+ end
195
+ hash
196
+ end
197
+
198
+ def hash_values_by_keys(value_key, *hash_keys)
199
+ last_key = hash_keys.pop
200
+ raise 'no keys given' unless last_key
201
+
202
+ hash = {}
203
+ each do |item|
204
+ h = hash
205
+ hash_keys.each do |key|
206
+ h = (h[item[key]] ||= {})
207
+ end
208
+ h[item[hash_key]] = item[value_key]
209
+ end
210
+ hash
211
+ end
212
+
213
+ def hash_arrays_by_key(key)
214
+ hash = {}
215
+ each do |item|
216
+ (hash[item[key]] ||= []) << item
217
+ end
218
+ hash
219
+ end
220
+
221
+ def hash_arrays_by_keys(*keys)
222
+ last_key = keys.pop
223
+ hash = {}
224
+ each do |item|
225
+ h = hash
226
+ keys.each do |key|
227
+ h = (h[item[key]] ||= {})
228
+ end
229
+ (h[item[last_key]] ||= []) << item
230
+ end
231
+ hash
232
+ end
233
+
234
+ def hash_arrays_by_id
235
+ hash_arrays_by_key(:id)
236
+ end
237
+
238
+ def hash_with_keys(*args)
239
+ first_arg = args.first
240
+ args = first_arg if first_arg.is_a?(Array)
241
+
242
+ hash = {}
243
+ (0...length).each do |i|
244
+ hash[args[i]] = self[i]
245
+ end
246
+ hash
247
+ end
248
+
249
+ def hash_pairs
250
+ Hash[*self]
251
+ end
252
+
253
+ def ids
254
+ map {|item| item.id }
255
+ end
256
+
257
+ def join_unless_empty(delimiter, none = 'None')
258
+ self.empty? ? none : self.join(delimiter)
259
+ end
260
+
261
+ def conjoin(conjunction = 'and')
262
+ size = self.size
263
+ return join if size < 2
264
+ return join(" #{conjunction} ") if size == 2
265
+
266
+ array = clone
267
+ last = array.pop
268
+ "#{array.join(', ')}, #{conjunction} #{last}"
269
+ end
270
+ alias_method :join_with_conjunction, :conjoin
271
+
272
+ def to_table(*columns)
273
+ options = columns.last.is_a?(Hash) ? columns.pop : {}
274
+ raise 'no columns specified' if columns.empty?
275
+
276
+ column_delimiter = options[:column_delimiter] || "\t"
277
+ line_delimiter = options[:line_delimiter] || "\r\n"
278
+
279
+ [
280
+ columns.map do |column|
281
+ column = column[0] if column.is_a?(Array)
282
+ column.is_a?(String) ? column : column.to_s.titlecase
283
+ end.flatten.join(column_delimiter),
284
+
285
+ *(map do |object|
286
+ columns.map do |column|
287
+ column = column[1] if column.is_a?(Array)
288
+ column.is_a?(Proc) ? column.call(object) : object[column]
289
+ end.flatten.join(column_delimiter)
290
+ end + [nil])
291
+ ].join(line_delimiter)
292
+ end
293
+ end
294
+
295
+ class Array
296
+ def of(unit)
297
+ size.of(unit)
298
+ end
299
+
300
+ def resize(new_size, filler = nil)
301
+ current_size = size
302
+ if new_size < current_size
303
+ slice!(new_size, current_size)
304
+ else
305
+ fill(filler, current_size, new_size - current_size)
306
+ end
307
+ self
308
+ end
309
+
310
+ # Convert array of function parameters to a single attributes hash.
311
+ # Each parameter must be a Symbol, except for the last, which may be
312
+ # a Symbol or an attributes hash.
313
+ def to_attrs
314
+ attrs = last.is_a?(Hash) ? pop : {}
315
+ self.each {|arg| attrs[arg] = true }
316
+ attrs
317
+ end
318
+ end
319
+
320
+ # Enables FixNum conversion to time duration values (in seconds).
321
+ class Fixnum
322
+ # 2.weeks = 14.days
323
+ def weeks
324
+ self * 7.days
325
+ end
326
+ # 1.days = 24.hours
327
+ def days
328
+ self * 24.hours
329
+ end
330
+ # 1.hours = 60.minutes = 3600 (seconds)
331
+ def hours
332
+ self * 60.minutes
333
+ end
334
+ # 1.minutes = 60 (seconds)
335
+ # 5.minutes = 300 (seconds)
336
+ def minutes
337
+ self * 60
338
+ end
339
+ # 5.minutes.ago = Time.now - 300
340
+ def ago
341
+ Time.now - self
342
+ end
343
+
344
+ # format thousands
345
+ def format_thousands
346
+ to_s.reverse.gsub(/(\d{3})/, '\1,').sub(/\,(-?)$/, '\1').reverse
347
+ end
348
+ end
349
+
350
+ TimeMethods = proc do
351
+ # timezone conversion
352
+ alias_method :old_zone, :zone
353
+ def zone
354
+ @timezone ? @timezone.period_for_utc(@utc).abbreviation.to_s : old_zone
355
+ end
356
+
357
+ def set_timezone!(timezone, utc)
358
+ @timezone = timezone
359
+ @utc = utc
360
+ self
361
+ end
362
+
363
+ def to_timezone(timezone)
364
+ timezone = TZInfo::Timezone.get(timezone) if timezone.is_a?(String)
365
+ timezone.utc_to_local(to_utc).set_timezone!(timezone, self)
366
+ end
367
+
368
+ def from_timezone(timezone)
369
+ timezone = TZInfo::Timezone.get(timezone) if timezone.is_a?(String)
370
+ timezone.local_to_utc(self)
371
+ end
372
+
373
+ def to_utc
374
+ @timezone ? from_timezone(@timezone) : self
375
+ end
376
+
377
+ # string representations
378
+
379
+ def md
380
+ strftime("%m/%d")
381
+ end
382
+
383
+ def md_full
384
+ strftime("%B %e")
385
+ end
386
+ alias_method :md_long, :md_full
387
+
388
+ def md_abbrev
389
+ strftime("%b %e")
390
+ end
391
+
392
+ def my_full
393
+ strftime("%B %Y")
394
+ end
395
+ alias_method :my_long, :my_full
396
+
397
+ def my_abbrev
398
+ strftime("%b %Y")
399
+ end
400
+
401
+ def mdy
402
+ strftime("%m/%d/%Y")
403
+ end
404
+ def ymd
405
+ strftime("%Y-%m-%d")
406
+ end
407
+
408
+ def mdy_full
409
+ strftime("%B %e, %Y")
410
+ end
411
+ alias_method :mdy_long, :mdy_full
412
+
413
+ def mdy_abbrev
414
+ strftime("%b %e, %Y")
415
+ end
416
+
417
+ def mdy_hm
418
+ strftime("%m/%d/%Y %l:%M %p")
419
+ end
420
+ def mdy_hmz
421
+ strftime("%m/%d/%Y %l:%M %p " + zone)
422
+ end
423
+ def mdy_hms
424
+ strftime("%m/%d/%Y %l:%M:%S %p")
425
+ end
426
+ def mdy_hmsz
427
+ strftime("%m/%d/%Y %l:%M:%S %p " + zone)
428
+ end
429
+
430
+ def ymd_hm
431
+ strftime("%Y-%m-%d %l:%M %p")
432
+ end
433
+ def ymd_hmz
434
+ strftime("%Y-%m-%d %l:%M %p " + zone)
435
+ end
436
+ def ymd_hms
437
+ strftime("%Y-%m-%d %l:%M:%S %p")
438
+ end
439
+ def ymd_hmsz
440
+ strftime("%Y-%m-%d %l:%M:%S %p " + zone)
441
+ end
442
+
443
+ def sql
444
+ strftime("%Y-%m-%d %H:%M:%S")
445
+ end
446
+
447
+ def mdy_hm_full
448
+ strftime("%B %e, %Y, %l:%M %p")
449
+ end
450
+ alias_method :mdy_hm_long, :mdy_hm_full
451
+ def mdy_hmz_full
452
+ strftime("%B %e, %Y, %l:%M %p " + zone)
453
+ end
454
+ alias_method :mdy_hmz_long, :mdy_hmz_full
455
+
456
+ def mdy_at_hm_full
457
+ strftime("%B %e, %Y at %l:%M %p")
458
+ end
459
+ alias_method :mdy_at_hm_long, :mdy_at_hm_full
460
+ def mdy_at_hmz_full
461
+ strftime("%B %e, %Y at %l:%M %p " + zone)
462
+ end
463
+ alias_method :mdy_at_hmz_long, :mdy_at_hmz_full
464
+
465
+ def hm
466
+ strftime("%l:%M %p")
467
+ end
468
+ def hmz
469
+ strftime("%l:%M %p " + zone)
470
+ end
471
+ def hms
472
+ strftime("%l:%M:%S %p")
473
+ end
474
+ def hmsz
475
+ strftime("%l:%M:%S %p " + zone)
476
+ end
477
+
478
+ def hm_mdy
479
+ strftime("%l:%M %p, %m/%d/%Y")
480
+ end
481
+ def hmz_mdy
482
+ strftime("%l:%M %p #{zone}, %m/%d/%Y")
483
+ end
484
+ def hms_mdy
485
+ strftime("%l:%M:%S %p, %m/%d/%Y")
486
+ end
487
+ def hmsz_mdy
488
+ strftime("%l:%M:%S %p #{zone}, %m/%d/%Y")
489
+ end
490
+
491
+ def hm_mdy_full
492
+ strftime("%l:%M %p, %B %e, %Y")
493
+ end
494
+ alias_method :hm_mdy_long, :hm_mdy_full
495
+ def hmz_mdy_full
496
+ strftime("%l:%M %p #{zone}, %B %e, %Y")
497
+ end
498
+ alias_method :hmz_mdy_long, :hmz_mdy_full
499
+
500
+ def zero?; false; end
501
+
502
+ def is_between?(start_date, end_date)
503
+ self >= start_date && self < end_date
504
+ end
505
+ end
506
+
507
+ Date.class_eval &TimeMethods
508
+ Time.class_eval &TimeMethods
509
+
510
+ class NilClass
511
+ def mdy; ''; end
512
+ def mdy_hm; ''; end
513
+ def mdy_hms; ''; end
514
+ def mdy_hm_full; ''; end
515
+ def mdy_hm_long; ''; end
516
+ def hm_mdy; ''; end
517
+ def hms_mdy; ''; end
518
+ def hm_mdy_full; ''; end
519
+ def hm_mdy_long; ''; end
520
+ def ymd_hm; ''; end
521
+ def ymd_hms; ''; end
522
+ def sql; ''; end
523
+
524
+ def to_dollars; ''; end
525
+ def to_dollars_cents; ''; end
526
+
527
+ def size; 0; end
528
+ def length; 0; end
529
+
530
+ def empty?; true; end
531
+ def blank?; true; end
532
+ def zero?; true; end
533
+
534
+ def unless_blank(blank_value); blank_value; end
535
+
536
+ # Ruby default: nil.id == 4 (now deprecated, but still present in 1.8)
537
+ # can cause unexpected results when using id from nil Sequel results
538
+ # so we'll raise an error instead, effectively undefining 'id' method
539
+ def id
540
+ raise NoMethodError, "undefined method `id' for nil:NilClass"
541
+ end
542
+ end
543
+
544
+ class String
545
+ class << self
546
+ # Generates string of random text of the specified length.
547
+ def random(length)
548
+ chars = [('A'..'H'), ('J'..'N'), ('P'..'R'), ('T'..'Y'), ('3'..'4'), ('6'..'9')].map {|r| r.to_a }.flatten # array
549
+
550
+ text = ''
551
+ size = chars.size
552
+ length.times { text += chars[rand(size)] }
553
+
554
+ text
555
+ end
556
+ end
557
+
558
+ def commaize(places = 3)
559
+ s = reverse
560
+ while s.sub!(/(\d{3})(\d)/, '\1,\2')
561
+ next
562
+ end
563
+ s.reverse
564
+ end
565
+ def blank?
566
+ self !~ /\S/
567
+ end
568
+
569
+ def to_const(klass = Kernel)
570
+ begin
571
+ parts = self.split(/::/)
572
+ while (next_part = parts.shift)
573
+ klass = klass.const_get(next_part)
574
+ end
575
+
576
+ klass
577
+ rescue
578
+ raise "Constant '#{self}' not defined"
579
+ end
580
+ end
581
+
582
+ def content_length
583
+ bytesize rescue length
584
+ end
585
+
586
+ def remove_tags
587
+ gsub(/<\/?[^>]*>/, "")
588
+ end
589
+ alias_method :strip_tags, :remove_tags
590
+
591
+ # adapt underscore method to convert whitespace to underscores
592
+ alias_method :original_sequel_underscore, :underscore
593
+ def underscore
594
+ original_sequel_underscore.gsub(/\W+/, '_').sub(/_\Z/, '')
595
+ end
596
+
597
+ # adapt titlecase method to downcase short prepositions in middle
598
+ alias_method :original_sequel_titlecase, :titlecase
599
+ def titlecase
600
+ original_sequel_titlecase.gsub(/(\S)( +)(at|for|in|of|on|to)( +)(\S)/i) {"#{$1}#{$2}#{$3.downcase}#{$4}#{$5}"}
601
+ end
602
+ alias_method :titleize, :titlecase
603
+
604
+ def adjective
605
+ gsub(/\s/, '-')
606
+ end
607
+ alias_method :adjectivize, :adjective
608
+
609
+ def unless_blank(alt_value = nil)
610
+ self.blank? ? alt_value : self
611
+ end
612
+ def unless_equal_to(value, alt_value = nil)
613
+ (self == value) ? alt_value : self
614
+ end
615
+
616
+ # Prepend addition just inside the specified tag of document.
617
+ def prepend_html(addition, tag)
618
+ regexp = /(\<#{tag}[^\>]*\>)/i
619
+ self =~ regexp ? sub(regexp, '\1' + addition) : (addition + self)
620
+ end
621
+
622
+ def html_escape
623
+ Rack::Utils.escape_html(self)
624
+ end
625
+
626
+ def url_escape
627
+ # encode space to '+'; don't encode letters, numbers, periods
628
+ gsub(/([^\w\.])/) { sprintf("%%%02X", $&.unpack("C")[0]) }
629
+ end
630
+ alias_method :url_encode, :url_escape
631
+
632
+ # Adapted from Rack::Utils.unescape.
633
+ def url_unescape
634
+ self.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/n) do
635
+ [$1.delete('%')].pack('H*')
636
+ end
637
+ end
638
+
639
+ # Validates value against specified format.
640
+ # If required is true, value must contain a non-whitespace character.
641
+ # If required is false, value need not match format if and only if value contains only whitespace.
642
+ def validate(format, options = {})
643
+ label = options[:label] || 'value'
644
+ if options[:required] && (self.blank?)
645
+ # value required
646
+ raise "#{label} required and missing"
647
+ elsif format && !self.blank?
648
+ format_class = Kiss::Format.lookup(format)
649
+
650
+ begin
651
+ format_class.validate(self, options[:context] || {})
652
+ rescue Kiss::Format::ValidateError => e
653
+ raise "validation error on #{label}: #{e.message}"
654
+ end
655
+ end
656
+ end
657
+ end
658
+
659
+ class Symbol
660
+ def to_const
661
+ self.to_s.to_const
662
+ end
663
+ end
664
+
665
+ class Numeric
666
+ def commaize
667
+ to_s.commaize
668
+ end
669
+
670
+ def to_places(num_places)
671
+ sprintf("%0.#{num_places}f", self)
672
+ end
673
+
674
+ def to_dollars
675
+ commaize
676
+ end
677
+
678
+ def to_dollars_cents
679
+ to_places(2).commaize
680
+ end
681
+ alias_method :format_currency, :to_dollars_cents
682
+
683
+ def quantity(unit)
684
+ "#{self.commaize} #{ self != 1 ? unit.pluralize : unit }"
685
+ end
686
+ alias_method :of, :quantity
687
+ end
688
+
689
+ module Digest
690
+ {
691
+ :MD5 => "digest/md5",
692
+ :RMD160 => "digest/rmd160",
693
+ :SHA1 => "digest/sha1",
694
+ :SHA256 => "digest/sha2",
695
+ :SHA384 => "digest/sha2",
696
+ :SHA512 => "digest/sha2"
697
+ }.each_pair do |type, path|
698
+ autoload type, path
699
+ end
700
+ end