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