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