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.
- data/LICENSE +1 -1
- data/Rakefile +2 -1
- data/VERSION +1 -1
- data/bin/kiss +151 -34
- data/data/scaffold.tgz +0 -0
- data/lib/kiss.rb +389 -742
- data/lib/kiss/accessors/controller.rb +47 -0
- data/lib/kiss/accessors/request.rb +106 -0
- data/lib/kiss/accessors/template.rb +23 -0
- data/lib/kiss/action.rb +502 -132
- data/lib/kiss/bench.rb +14 -5
- data/lib/kiss/debug.rb +14 -6
- data/lib/kiss/exception_report.rb +22 -299
- data/lib/kiss/ext/core.rb +700 -0
- data/lib/kiss/ext/rack.rb +33 -0
- data/lib/kiss/ext/sequel_database.rb +47 -0
- data/lib/kiss/ext/sequel_mysql_dataset.rb +23 -0
- data/lib/kiss/form.rb +404 -179
- data/lib/kiss/form/field.rb +183 -307
- data/lib/kiss/form/field_types.rb +239 -0
- data/lib/kiss/format.rb +88 -70
- data/lib/kiss/html/exception_report.css +222 -0
- data/lib/kiss/html/exception_report.html +210 -0
- data/lib/kiss/iterator.rb +14 -12
- data/lib/kiss/login.rb +8 -8
- data/lib/kiss/mailer.rb +68 -66
- data/lib/kiss/model.rb +323 -36
- data/lib/kiss/rack/bench.rb +16 -8
- data/lib/kiss/rack/email_errors.rb +25 -15
- data/lib/kiss/rack/errors_ok.rb +2 -2
- data/lib/kiss/rack/facebook.rb +6 -6
- data/lib/kiss/rack/file_not_found.rb +10 -8
- data/lib/kiss/rack/log_exceptions.rb +3 -3
- data/lib/kiss/rack/recorder.rb +2 -2
- data/lib/kiss/rack/show_debug.rb +2 -2
- data/lib/kiss/rack/show_exceptions.rb +2 -2
- data/lib/kiss/request.rb +435 -0
- data/lib/kiss/sequel_session.rb +15 -14
- data/lib/kiss/static_file.rb +20 -13
- data/lib/kiss/template.rb +327 -0
- metadata +60 -25
- data/lib/kiss/controller_accessors.rb +0 -81
- data/lib/kiss/hacks.rb +0 -188
- data/lib/kiss/sequel_mysql.rb +0 -25
- data/lib/kiss/template_methods.rb +0 -167
data/lib/kiss/bench.rb
CHANGED
@@ -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 += @
|
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
|
-
<
|
37
|
-
<
|
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(/\</, '<')} - 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
|
-
|
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://" +
|
58
|
+
textmate_url = "txmt://open?url=file://" + (Kiss.absolute_path(filename).html_escape) + '&line=' + line
|
50
59
|
%Q(<a href="#{textmate_url}">#{filename}:#{line}</a> #{method})
|
51
60
|
end
|
52
61
|
end
|
data/lib/kiss/debug.rb
CHANGED
@@ -18,27 +18,35 @@ font-size: 12px;
|
|
18
18
|
color: #101;
|
19
19
|
}
|
20
20
|
.kiss_debug a {
|
21
|
-
color: #
|
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 += @
|
38
|
+
html += @_debug_messages.map do |object, context|
|
31
39
|
filename, line, method = context.split(/:/)
|
32
|
-
textmate_url = "txmt://open?url=file://" + Kiss.
|
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
|
-
<
|
36
|
-
<
|
43
|
+
<small>kiss debug at <a href="#{textmate_url}">#{filename}:#{line}</a> #{method}</small>
|
44
|
+
<tt><b>#{object.gsub(/\</, '<')}</b></tt>
|
37
45
|
</div>
|
38
46
|
EOT
|
39
47
|
end.join
|
40
48
|
|
41
|
-
|
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
|
-
|
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(
|
66
|
-
|
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
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
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
|