kiss 0.9
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 +19 -0
- data/Rakefile +33 -0
- data/VERSION +1 -0
- data/lib/kiss/action.rb +204 -0
- data/lib/kiss/controller_accessors.rb +101 -0
- data/lib/kiss/exception_report.rb +359 -0
- data/lib/kiss/form/field.rb +296 -0
- data/lib/kiss/form.rb +414 -0
- data/lib/kiss/format.rb +80 -0
- data/lib/kiss/hacks.rb +140 -0
- data/lib/kiss/iterator.rb +56 -0
- data/lib/kiss/mailer.rb +92 -0
- data/lib/kiss/model.rb +114 -0
- data/lib/kiss/rack/bench.rb +131 -0
- data/lib/kiss/rack/email_errors.rb +64 -0
- data/lib/kiss/rack/facebook.rb +28 -0
- data/lib/kiss/rack/file_not_found.rb +42 -0
- data/lib/kiss/rack/log_exceptions.rb +23 -0
- data/lib/kiss/rack/show_debug.rb +82 -0
- data/lib/kiss/rack/show_exceptions.rb +27 -0
- data/lib/kiss/sequel_mysql.rb +23 -0
- data/lib/kiss/sequel_session.rb +166 -0
- data/lib/kiss/template_methods.rb +125 -0
- data/lib/kiss.rb +725 -0
- metadata +108 -0
data/lib/kiss/form.rb
ADDED
@@ -0,0 +1,414 @@
|
|
1
|
+
class Kiss
|
2
|
+
class Form
|
3
|
+
module AttributesSetter
|
4
|
+
def set_attributes(attrs_original,required = [])
|
5
|
+
unless attrs_original.is_a?(Hash) then
|
6
|
+
raise "first parameter must be a hash of attributes: #{attrs_original}"
|
7
|
+
end
|
8
|
+
|
9
|
+
attrs = attrs_original.clone
|
10
|
+
|
11
|
+
required.each do |key|
|
12
|
+
raise "missing required parameter '#{key}'" unless attrs[key]
|
13
|
+
send("#{key}=", attrs[key])
|
14
|
+
attrs.delete(key)
|
15
|
+
end
|
16
|
+
|
17
|
+
attrs.each_pair do |key,value|
|
18
|
+
send("#{key}=", value)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
include Kiss::Form::AttributesSetter
|
23
|
+
|
24
|
+
require 'kiss/form/field';
|
25
|
+
|
26
|
+
attr_accessor(
|
27
|
+
:name,:params,:submitted,:action,:controller,:id,:url,:request,
|
28
|
+
:object,:fields,:fields_hash,:method,:enctype,:errors,:submit,:cancel,:style,:class,
|
29
|
+
:has_field_errors,:error_class,:field_error_class,:mark_required,:object
|
30
|
+
)
|
31
|
+
|
32
|
+
|
33
|
+
@@field_types = {
|
34
|
+
:text => TextField,
|
35
|
+
:hidden => HiddenField,
|
36
|
+
:textarea => TextAreaField,
|
37
|
+
:password => PasswordField,
|
38
|
+
#:multitext => MultiTextField,
|
39
|
+
:boolean => BooleanField,
|
40
|
+
:file => FileField,
|
41
|
+
:select => SelectField,
|
42
|
+
:radio => RadioField,
|
43
|
+
:checkbox => CheckboxField,
|
44
|
+
:multiselect => MultiSelectField,
|
45
|
+
:submit => SubmitField,
|
46
|
+
}
|
47
|
+
|
48
|
+
|
49
|
+
# Creates a new form object with specified attributes.
|
50
|
+
def initialize(attrs)
|
51
|
+
set_attributes(attrs,[:name,:action])
|
52
|
+
|
53
|
+
raise "Missing required option 'name'" unless @name && (@name != '')
|
54
|
+
|
55
|
+
@method ||= 'post'
|
56
|
+
|
57
|
+
# move field definitions to different var
|
58
|
+
# want to use @fields for array of Field objects
|
59
|
+
@fields_specs = @fields || []
|
60
|
+
|
61
|
+
@fields = []
|
62
|
+
@fields_hash = {}
|
63
|
+
@default_values ||= {}
|
64
|
+
@params = {}
|
65
|
+
|
66
|
+
@fields_specs.each do |field|
|
67
|
+
# create field here
|
68
|
+
add_field(field)
|
69
|
+
end
|
70
|
+
|
71
|
+
@errors = []
|
72
|
+
@field_errors = {}
|
73
|
+
end
|
74
|
+
|
75
|
+
# Adds a section break, which causes form's table to close and new table to open when form is rendered.
|
76
|
+
def add_section_break
|
77
|
+
@fields << :section_break
|
78
|
+
end
|
79
|
+
|
80
|
+
# Creates and adds a field to the form, according to specified attributes.
|
81
|
+
def add_field(attrs)
|
82
|
+
raise "added second field named '#{attrs[:name]}'; field names must be unique" if @fields_hash[attrs[:name]]
|
83
|
+
|
84
|
+
attrs[:type] ||= :text
|
85
|
+
type = attrs[:type].to_sym
|
86
|
+
unless @@field_types.has_key?(type)
|
87
|
+
type = attrs[:type] = :text
|
88
|
+
end
|
89
|
+
|
90
|
+
@has_required_fields ||= attrs[:required]
|
91
|
+
|
92
|
+
field_class = @@field_types[type]
|
93
|
+
field = field_class.new(attrs)
|
94
|
+
|
95
|
+
field_name = field.name.to_s
|
96
|
+
field.form = self
|
97
|
+
|
98
|
+
unless type == :submit
|
99
|
+
@fields << field
|
100
|
+
@fields_hash[field_name] = field
|
101
|
+
end
|
102
|
+
|
103
|
+
@enctype = 'multipart/form-data' if field.type == :file
|
104
|
+
|
105
|
+
# if param submitted, set value of field to match
|
106
|
+
if (param = @params[field_name])
|
107
|
+
field.value = param
|
108
|
+
elsif @object && !attrs.has_key?(:value)
|
109
|
+
value = @object[field_name.to_sym] || @object[field_name]
|
110
|
+
|
111
|
+
case field.format
|
112
|
+
when :date:
|
113
|
+
field.value = value ? value.strftime("%m/%d/%Y") : ''
|
114
|
+
when :datetime:
|
115
|
+
field.value = value ? value.strftime("%m/%d/%Y %I:%M%p") : ''
|
116
|
+
else
|
117
|
+
field.value = value.to_s
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
field
|
122
|
+
end
|
123
|
+
|
124
|
+
# Creates and adds set of submit buttons to the form, per specified attributes.
|
125
|
+
def add_submit(attrs)
|
126
|
+
@submit = add_field({
|
127
|
+
:type => :submit,
|
128
|
+
:name => 'submit',
|
129
|
+
:save => false
|
130
|
+
}.merge(attrs) )
|
131
|
+
end
|
132
|
+
|
133
|
+
# Gets hash of form values ready to be saved to Sequel::Model object.
|
134
|
+
def sequel_values
|
135
|
+
@sequel_values ||= begin
|
136
|
+
hash = {}
|
137
|
+
@fields.each {|field| hash[field.name] = field.sequel_value unless field == :section_break }
|
138
|
+
hash
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
# Gets hash of form values.
|
143
|
+
def values
|
144
|
+
@values ||= begin
|
145
|
+
hash = {}
|
146
|
+
@fields.each {|field| hash[field.name] = field.value unless field == :section_break}
|
147
|
+
hash
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
# Add error message to be rendered with form.
|
152
|
+
# If multiple args given, first arg is field name, and second arg (error message)
|
153
|
+
# will render next to specified field.
|
154
|
+
def add_error(*args)
|
155
|
+
if args.size > 2
|
156
|
+
raise 'too many args'
|
157
|
+
elsif args.size == 0
|
158
|
+
raise 'at least one arg required'
|
159
|
+
end
|
160
|
+
|
161
|
+
# args.size == 1 or 2
|
162
|
+
if args.size == 1
|
163
|
+
if args[0].is_a?(String)
|
164
|
+
@errors << args[0]
|
165
|
+
return
|
166
|
+
else
|
167
|
+
field_name = args[0].field_name.to_s
|
168
|
+
message = args[0].message
|
169
|
+
end
|
170
|
+
else # args.size == 2
|
171
|
+
# args == [field_name, message]
|
172
|
+
field_name = args[0].to_s
|
173
|
+
message = args[1]
|
174
|
+
end
|
175
|
+
return @fields_hash[field_name].add_error(message)
|
176
|
+
end
|
177
|
+
|
178
|
+
# Validates form values against fields' format and required attributes,
|
179
|
+
# and returns values unless form has errors.
|
180
|
+
def validate
|
181
|
+
return nil unless submitted
|
182
|
+
|
183
|
+
@fields.each {|field| field.validate unless field == :section_break }
|
184
|
+
|
185
|
+
has_errors ? nil : values
|
186
|
+
end
|
187
|
+
|
188
|
+
# Saves form values to Sequel::Model object.
|
189
|
+
def save(object = @object)
|
190
|
+
@fields.each do |field|
|
191
|
+
next if field == :section_break
|
192
|
+
# ignore fields whose name starts with underscore
|
193
|
+
next if field.name =~ /\A\_/
|
194
|
+
# don't save 'ignore' fields to the database
|
195
|
+
next if field.ignore || !field.save
|
196
|
+
|
197
|
+
object[field.name.to_sym] = field.sequel_value
|
198
|
+
end
|
199
|
+
|
200
|
+
object.save
|
201
|
+
end
|
202
|
+
|
203
|
+
# Returns true if form has errors.
|
204
|
+
def has_errors
|
205
|
+
(@errors.size > 0 || @has_field_errors)
|
206
|
+
end
|
207
|
+
|
208
|
+
# Checks whether form was submitted and accepted by user and, if so,
|
209
|
+
# whether form validates.
|
210
|
+
# If form validates, saves form values to form's Sequel::Model object
|
211
|
+
# and returns true. Otherwise, returns false.
|
212
|
+
def process(object = @object)
|
213
|
+
return false unless submitted
|
214
|
+
|
215
|
+
if accepted
|
216
|
+
validate
|
217
|
+
return false if has_errors
|
218
|
+
|
219
|
+
save(object)
|
220
|
+
end
|
221
|
+
|
222
|
+
return true
|
223
|
+
end
|
224
|
+
|
225
|
+
# Returns true if user submitted form with non-cancel submit button
|
226
|
+
# (non-nil submit param, not equal to cancel submit button value).
|
227
|
+
def accepted
|
228
|
+
raise 'form missing submit field' unless @submit
|
229
|
+
return params[@submit.name] != @submit.cancel
|
230
|
+
end
|
231
|
+
|
232
|
+
# Renders error HTML block for top of form, and returns as string.
|
233
|
+
def errors_html
|
234
|
+
return nil unless has_errors
|
235
|
+
|
236
|
+
@errors << "Please remedy the errors shown below." if @has_field_errors
|
237
|
+
|
238
|
+
if @errors.size == 1
|
239
|
+
content = @errors[0]
|
240
|
+
else
|
241
|
+
content = "<ul>" + @errors.map {|e| "<li>#{e}</li>"}.join + "</ul>"
|
242
|
+
end
|
243
|
+
|
244
|
+
@errors.pop if @has_field_errors
|
245
|
+
|
246
|
+
plural = @errors.size > 1 || @has_field_errors ? 's' : nil
|
247
|
+
%Q(<table border=0 cellspacing=0><tr class="kiss_error"><th>Error#{plural}</th></td><td class="kiss_required"></td><td>#{content}</td></tr></table>)
|
248
|
+
end
|
249
|
+
|
250
|
+
# Renders current action using form's HTML as action render content.
|
251
|
+
def render(options = {})
|
252
|
+
@request.render options.merge(:content => html)
|
253
|
+
end
|
254
|
+
|
255
|
+
# Renders beginning of form (form open tag and form/field/error styles).
|
256
|
+
def html_open
|
257
|
+
@error_class ||= 'kiss_form_error_message'
|
258
|
+
@field_error_class ||= @error_class
|
259
|
+
|
260
|
+
# form tag
|
261
|
+
form_attrs = ['method','enctype','class','style'].map do |attr|
|
262
|
+
"#{attr}=\"#{send attr}\""
|
263
|
+
end.join(' ')
|
264
|
+
form_tag = %Q(<form action="#{@action}" #{form_attrs}><input type=hidden name="form" value="#{@name}">)
|
265
|
+
|
266
|
+
# style tag
|
267
|
+
styles = []
|
268
|
+
styles.push( <<-EOT
|
269
|
+
.kiss_form table {
|
270
|
+
margin-bottom: 6px;
|
271
|
+
}
|
272
|
+
.kiss_form td {
|
273
|
+
padding: 2px 4px;
|
274
|
+
}
|
275
|
+
.kiss_form tr.kiss_error {
|
276
|
+
background-color: #ff8;
|
277
|
+
}
|
278
|
+
.kiss_form tr.kiss_error th {
|
279
|
+
vertical-align: top;
|
280
|
+
padding: 2px 4px 3px 4px;
|
281
|
+
text-align: right;
|
282
|
+
background-color: #fc7;
|
283
|
+
color: #900;
|
284
|
+
border: 1px solid #f96;
|
285
|
+
border-right: none;
|
286
|
+
}
|
287
|
+
.kiss_form tr.kiss_error td {
|
288
|
+
vertical-align: top;
|
289
|
+
padding: 2px 6px 3px 6px;
|
290
|
+
border: 1px solid #f96;
|
291
|
+
border-left: none;
|
292
|
+
color: #000;
|
293
|
+
}
|
294
|
+
.kiss_form tr.kiss_error ul {
|
295
|
+
padding-left: 16px;
|
296
|
+
margin: 0;
|
297
|
+
}
|
298
|
+
.kiss_form tr.kiss_error td.kiss_required {
|
299
|
+
padding: 0;
|
300
|
+
border-right: none;
|
301
|
+
}
|
302
|
+
.kiss_form .kiss_help {
|
303
|
+
padding: 0px 3px 8px 6px;
|
304
|
+
font-size: 90%;
|
305
|
+
color: #555;
|
306
|
+
}
|
307
|
+
.kiss_form td.kiss_required {
|
308
|
+
padding: 2px 0px;
|
309
|
+
color: #c00;
|
310
|
+
}
|
311
|
+
.kiss_form td.kiss_label {
|
312
|
+
text-align: right;
|
313
|
+
white-space: nowrap;
|
314
|
+
}
|
315
|
+
.kiss_form tr.kiss_prompt td {
|
316
|
+
padding: 8px 3px 2px 4px;
|
317
|
+
}
|
318
|
+
.kiss_form tr.kiss_submit td.kiss_submit {
|
319
|
+
padding: 6px 3px;
|
320
|
+
}
|
321
|
+
.kiss_form input[type="text"],.kiss_form textarea {
|
322
|
+
width: 250px;
|
323
|
+
}
|
324
|
+
EOT
|
325
|
+
)
|
326
|
+
styles.push( <<-EOT
|
327
|
+
.kiss_form_error_message {
|
328
|
+
padding: 1px 4px 2px 4px;
|
329
|
+
border: 1px solid #f96;
|
330
|
+
background-color: #ff8;
|
331
|
+
color: #900;
|
332
|
+
font-size: 85%;
|
333
|
+
display: block;
|
334
|
+
float: left;
|
335
|
+
margin-top: 1px;
|
336
|
+
|
337
|
+
}
|
338
|
+
.kiss_form_error_message div {
|
339
|
+
color: #c00;
|
340
|
+
font-weight: bold;
|
341
|
+
}
|
342
|
+
.kiss_form_error_message ul {
|
343
|
+
padding-left: 16px;
|
344
|
+
margin: 0;
|
345
|
+
}
|
346
|
+
EOT
|
347
|
+
) if @error_class == 'kiss_form_error_message'
|
348
|
+
style_tag = styles.size == 0 ? '' : "<style>" + styles.join('') + "</style>"
|
349
|
+
|
350
|
+
# combine
|
351
|
+
return %Q(#{form_tag}#{style_tag}<div class="kiss_form">#{errors_html})
|
352
|
+
end
|
353
|
+
|
354
|
+
# Renders end of form (form close tag).
|
355
|
+
def html_close
|
356
|
+
'</div></form>'
|
357
|
+
end
|
358
|
+
|
359
|
+
# Renders form fields HTML.
|
360
|
+
def fields_html
|
361
|
+
@fields.map do |field|
|
362
|
+
field == :section_break ? begin
|
363
|
+
table_html_close + table_html_open
|
364
|
+
end : field_html(field)
|
365
|
+
end.join
|
366
|
+
end
|
367
|
+
|
368
|
+
# Renders open of form table.
|
369
|
+
def table_html_open
|
370
|
+
%Q(<table border=0 cellspacing=0>) +
|
371
|
+
(@has_required_fields && @mark_required ? %Q( <tr><td></td><td class="kiss_required">*</td><td colspan=2 class="kiss_help">Required field.</td></tr> ) : '')
|
372
|
+
end
|
373
|
+
|
374
|
+
# Renders close of form table.
|
375
|
+
def table_html_close
|
376
|
+
'</table>'
|
377
|
+
end
|
378
|
+
|
379
|
+
# Renders form submit buttons.
|
380
|
+
def submit_html
|
381
|
+
field_html(@submit)
|
382
|
+
end
|
383
|
+
|
384
|
+
# Renders complete form HTML.
|
385
|
+
def html
|
386
|
+
return [
|
387
|
+
html_open,
|
388
|
+
table_html_open,
|
389
|
+
fields_html,
|
390
|
+
submit_html,
|
391
|
+
table_html_close,
|
392
|
+
html_close
|
393
|
+
].flatten.join
|
394
|
+
end
|
395
|
+
|
396
|
+
# Renders HTML for specified form field.
|
397
|
+
def field_html(field)
|
398
|
+
type = field.type
|
399
|
+
prompt = field.prompt.to_s
|
400
|
+
label = field.label.to_s
|
401
|
+
|
402
|
+
[
|
403
|
+
(prompt != '' ? %Q(<tr class="kiss_prompt"><td colspan=2></td><td colspan=2>#{prompt}</td></tr>) : ''),
|
404
|
+
"",
|
405
|
+
%Q(<tr class="kiss_#{type}"><td class="kiss_label">),
|
406
|
+
label != '' && prompt == '' ? label + ':' : '',
|
407
|
+
'</td><td class="kiss_required">',
|
408
|
+
field.required ? @mark_required : '',
|
409
|
+
%Q(</td><td class="kiss_#{type}">),
|
410
|
+
field.html, "</td></tr>"
|
411
|
+
].join
|
412
|
+
end
|
413
|
+
end
|
414
|
+
end
|
data/lib/kiss/format.rb
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
class Kiss
|
2
|
+
# This class leftover from a previous version of Kiss and is not currently used.
|
3
|
+
# In fact, this file is not required from any other part of Kiss.
|
4
|
+
#
|
5
|
+
# But we're keeping it around in case it's useful for the anticipated rewrite of
|
6
|
+
# Kiss.validate_format.
|
7
|
+
class Format
|
8
|
+
def self.regexp
|
9
|
+
@@regexp
|
10
|
+
end
|
11
|
+
def self.error
|
12
|
+
@@error
|
13
|
+
end
|
14
|
+
|
15
|
+
class Integer < Kiss::Format
|
16
|
+
@regexp = /\A\-?\d+\Z/
|
17
|
+
@error = 'must be an integer'
|
18
|
+
end
|
19
|
+
|
20
|
+
class PositiveInteger < Kiss::Format
|
21
|
+
@regexp = /\A\d*[1-9]\d*\Z/
|
22
|
+
@error = 'must be a positive integer'
|
23
|
+
end
|
24
|
+
|
25
|
+
class UnsignedInteger < Kiss::Format
|
26
|
+
@regexp = /\A\d+\Z/
|
27
|
+
@error = 'must be a positive integer or zero'
|
28
|
+
end
|
29
|
+
|
30
|
+
class NegativeInteger < Kiss::Format
|
31
|
+
@regexp = /\A\-\d*[1-9]\d*\Z/
|
32
|
+
@error = 'must be a negative integer'
|
33
|
+
end
|
34
|
+
|
35
|
+
class AlphaNum
|
36
|
+
@regexp = /\A[a-z0-9]\Z/i
|
37
|
+
@error = 'allows only letters and numbers'
|
38
|
+
end
|
39
|
+
|
40
|
+
class Word
|
41
|
+
@regexp = /\A\w+\Z/
|
42
|
+
@error = 'allows only letters, numbers, and _'
|
43
|
+
end
|
44
|
+
|
45
|
+
class EmailAddress
|
46
|
+
@regexp = /\A[A-Z0-9._%+-]+\@([A-Z0-9-]+\.)+[A-Z]{2,4}\Z/i
|
47
|
+
@error = 'must be a valid email address'
|
48
|
+
end
|
49
|
+
|
50
|
+
class Date
|
51
|
+
@regexp = /\A\d+\D\d+(\D\d+)?\Z/
|
52
|
+
@error = 'must be a valid date'
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
@@classes = {
|
57
|
+
:integer => Kiss::Format::Integer,
|
58
|
+
|
59
|
+
:integer_positive => Kiss::Format::PositiveInteger,
|
60
|
+
:positive_integer => Kiss::Format::PositiveInteger,
|
61
|
+
:id => Kiss::Format::PositiveInteger,
|
62
|
+
|
63
|
+
:integer_unsigned => Kiss::Format::UnsignedInteger,
|
64
|
+
:unsigned_integer => Kiss::Format::UnsignedInteger,
|
65
|
+
:id_or_zero => Kiss::Format::UnsignedInteger,
|
66
|
+
:id_zero => Kiss::Format::UnsignedInteger,
|
67
|
+
|
68
|
+
:integer_negative => Kiss::Format::NegativeInteger,
|
69
|
+
:negative_integer => Kiss::Format::NegativeInteger,
|
70
|
+
|
71
|
+
:word => Kiss::Format::Word,
|
72
|
+
:alphanum => Kiss::Format::AlphaNum,
|
73
|
+
:email_address => Kiss::Format::EmailAddress,
|
74
|
+
:date => Kiss::Format::Date
|
75
|
+
}
|
76
|
+
def self.from_symbol(symbol)
|
77
|
+
return @@classes[symbol] || (raise "no format for #{symbol}")
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
data/lib/kiss/hacks.rb
ADDED
@@ -0,0 +1,140 @@
|
|
1
|
+
# This is a collection of hacks to enable various Ruby lnaguage enhancements
|
2
|
+
# and other features of Kiss.
|
3
|
+
|
4
|
+
# Placeholder; overloaded by Kiss Rack builder option ShowDebug.
|
5
|
+
def debug(*args); end
|
6
|
+
# Placeholder; overloaded by Kiss Rack builder option Bench.
|
7
|
+
def bench(*args); end
|
8
|
+
# Placeholder; to be overloaded by Kiss Rack builder option MultiBench
|
9
|
+
# (not yet implemented).
|
10
|
+
def multibench(*args); end
|
11
|
+
|
12
|
+
class Kiss
|
13
|
+
# Used when Kiss#file_cache called from Kiss::Action#file_cache.
|
14
|
+
# Caught by Kiss's Rack builder option FileNotFound.
|
15
|
+
class TemplateFileNotFound < LoadError; end
|
16
|
+
class FileNotFound < LoadError; end
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
# Methods to enable hash.attr == hash[attr] syntax,
|
21
|
+
# including hash.id and hash.type
|
22
|
+
class Hash
|
23
|
+
def method_missing(meth,*args)
|
24
|
+
if /=$/=~(meth=meth.id2name) then
|
25
|
+
self[meth[0...-1]] = (args.length<2 ? args[0] : args)
|
26
|
+
else
|
27
|
+
self[meth] || self[meth.to_sym]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
def id
|
31
|
+
self['id'] || self [:id]
|
32
|
+
end
|
33
|
+
def type
|
34
|
+
self['type'] || self [:type] || Hash
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Enables FixNum conversion to time duration values (in seconds).
|
39
|
+
class Fixnum
|
40
|
+
# 2.weeks = 14.days
|
41
|
+
def weeks
|
42
|
+
self * 7.days
|
43
|
+
end
|
44
|
+
# 1.days = 24.hours
|
45
|
+
def days
|
46
|
+
self * 24.hours
|
47
|
+
end
|
48
|
+
# 1.hours = 60.minutes = 3600 (seconds)
|
49
|
+
def hours
|
50
|
+
self * 60.minutes
|
51
|
+
end
|
52
|
+
# 1.minutes = 60 (seconds)
|
53
|
+
# 5.minutes = 300 (seconds)
|
54
|
+
def minutes
|
55
|
+
self * 60
|
56
|
+
end
|
57
|
+
# 5.minutes.ago = Time.now - 300
|
58
|
+
def ago
|
59
|
+
Time.now - self
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
class Date
|
64
|
+
# Returns string representing date in m/d/yyyy format
|
65
|
+
def mdy
|
66
|
+
sprintf('%d/%d/%04d',month,mday,year)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
class NilClass
|
71
|
+
def mdy
|
72
|
+
''
|
73
|
+
end
|
74
|
+
def size
|
75
|
+
0
|
76
|
+
end
|
77
|
+
def length
|
78
|
+
0
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# Corrections to sequel_core/core_sql.rb
|
83
|
+
class String
|
84
|
+
def to_sequel_time
|
85
|
+
self == '0000-00-00 00:00:00' ? nil : begin
|
86
|
+
Time.parse(self)
|
87
|
+
rescue Exception => e
|
88
|
+
raise Sequel::Error::InvalidValue, "Invalid time value '#{self}' (#{e.message})"
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# Converts a string into a Date object.
|
93
|
+
def to_date
|
94
|
+
begin
|
95
|
+
Date.parse(self)
|
96
|
+
rescue Exception => e
|
97
|
+
if (self == '0000-00-00')
|
98
|
+
nil
|
99
|
+
else
|
100
|
+
raise Sequel::Error::InvalidValue, "Invalid date value '#{self}' (#{e.message})"
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def to_const
|
106
|
+
begin
|
107
|
+
parts = self.split(/::/)
|
108
|
+
klass = Kernel
|
109
|
+
while (next_part = parts.shift)
|
110
|
+
klass = klass.const_get(next_part)
|
111
|
+
end
|
112
|
+
|
113
|
+
klass
|
114
|
+
rescue
|
115
|
+
raise "Constant '#{self}' not defined"
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
class Symbol
|
121
|
+
def to_const
|
122
|
+
self.to_s.to_const
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
module Rack
|
127
|
+
class Request
|
128
|
+
def server
|
129
|
+
url = scheme + "://"
|
130
|
+
url << host
|
131
|
+
|
132
|
+
if scheme == "https" && port != 443 ||
|
133
|
+
scheme == "http" && port != 80
|
134
|
+
url << ":#{port}"
|
135
|
+
end
|
136
|
+
|
137
|
+
url
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
class Kiss
|
2
|
+
# Enables `loop' features for certain template loops (while, for...in).
|
3
|
+
# Similar to features of Perl's Template::Iterator by Andy Wardley.
|
4
|
+
class Iterator
|
5
|
+
attr_reader :index, :collection
|
6
|
+
|
7
|
+
# Creates a new loop iterator.
|
8
|
+
def initialize(collection = nil)
|
9
|
+
@collection = collection
|
10
|
+
@index = -1
|
11
|
+
end
|
12
|
+
|
13
|
+
# Used by template erubis pre-processing voodoo to advance
|
14
|
+
# the iterator index. Not intended for any other use.
|
15
|
+
def increment
|
16
|
+
@index = @index + 1
|
17
|
+
end
|
18
|
+
|
19
|
+
# Return current iteration number, indexed from one instead of zero.
|
20
|
+
def count
|
21
|
+
@index + 1
|
22
|
+
end
|
23
|
+
|
24
|
+
# Returns true if this is loop's first iteration.
|
25
|
+
def first
|
26
|
+
@index == 0
|
27
|
+
end
|
28
|
+
|
29
|
+
# Returns true if this is the last iteration of a
|
30
|
+
# loop over a collection.
|
31
|
+
def last
|
32
|
+
count == size
|
33
|
+
end
|
34
|
+
|
35
|
+
# Return previous item in loop-iterated collection.
|
36
|
+
def prev
|
37
|
+
@collection ? @collection[index-1] : nil
|
38
|
+
end
|
39
|
+
|
40
|
+
# Return next item in loop-iterated collection.
|
41
|
+
def next
|
42
|
+
@collection ? @collection[index+1] : nil
|
43
|
+
end
|
44
|
+
|
45
|
+
# Returns index of loop's last iteration.
|
46
|
+
def max
|
47
|
+
@collection ? @collection.size-1 : nil
|
48
|
+
end
|
49
|
+
|
50
|
+
# Returns number of iterations in loop over a collection
|
51
|
+
# (size of the iterated collection).
|
52
|
+
def size
|
53
|
+
@collection ? @collection.size : nil
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|