kiss 0.9.4 → 1.0
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/VERSION +1 -1
- data/lib/kiss.rb +545 -446
- data/lib/kiss/action.rb +34 -56
- data/lib/kiss/bench.rb +54 -0
- data/lib/kiss/controller_accessors.rb +42 -26
- data/lib/kiss/debug.rb +45 -0
- data/lib/kiss/exception_report.rb +49 -44
- data/lib/kiss/form.rb +105 -86
- data/lib/kiss/form/field.rb +212 -98
- data/lib/kiss/format.rb +175 -37
- data/lib/kiss/hacks.rb +133 -22
- data/lib/kiss/mailer.rb +4 -16
- data/lib/kiss/model.rb +60 -28
- data/lib/kiss/rack/bench.rb +2 -87
- data/lib/kiss/rack/log_exceptions.rb +2 -11
- data/lib/kiss/rack/show_debug.rb +3 -71
- data/lib/kiss/rack/show_exceptions.rb +2 -15
- data/lib/kiss/sequel_mysql.rb +4 -2
- data/lib/kiss/static_file.rb +31 -0
- data/lib/kiss/template_methods.rb +4 -11
- metadata +5 -3
- data/lib/kiss/rack/file.rb +0 -0
data/lib/kiss/format.rb
CHANGED
@@ -1,80 +1,218 @@
|
|
1
1
|
class Kiss
|
2
|
-
# This class
|
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.
|
2
|
+
# This class is used to validate and parse strings into Sequel-ready values.
|
7
3
|
class Format
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
4
|
+
# used to raise exceptions when values don't validate to format regexp
|
5
|
+
class ValidateError < Exception; end
|
6
|
+
|
7
|
+
@regexp = //
|
8
|
+
@error = 'invalid format'
|
9
|
+
@legend = nil
|
10
|
+
@input_width = nil
|
11
|
+
@default = ''
|
12
|
+
|
13
|
+
class << self
|
14
|
+
attr_accessor :regexp, :error, :legend, :input_width, :default
|
15
|
+
|
16
|
+
# copy Format class instance variables to their subclasses
|
17
|
+
def inherited(subclass)
|
18
|
+
self.instance_variables.each do |var|
|
19
|
+
subclass.instance_variable_set(var, instance_variable_get(var))
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def parse(value)
|
24
|
+
value
|
25
|
+
end
|
26
|
+
|
27
|
+
def value_to_s(value)
|
28
|
+
value.to_s
|
29
|
+
end
|
30
|
+
|
31
|
+
def validate(value)
|
32
|
+
raise Kiss::Format::ValidateError, @error unless value.to_s =~ @regexp
|
33
|
+
parse(value)
|
34
|
+
end
|
13
35
|
end
|
14
36
|
|
15
|
-
class Integer <
|
37
|
+
class Integer < self
|
16
38
|
@regexp = /\A\-?\d+\Z/
|
17
39
|
@error = 'must be an integer'
|
40
|
+
@default = 0
|
41
|
+
|
42
|
+
class << self
|
43
|
+
def parse(value)
|
44
|
+
value.to_i
|
45
|
+
end
|
46
|
+
|
47
|
+
def validate(value)
|
48
|
+
# remove commas for thousands
|
49
|
+
value =~ /\d/ ? super(value.gsub(/,/,'')) : nil
|
50
|
+
end
|
51
|
+
|
52
|
+
def value_to_s(value)
|
53
|
+
# add commas for thousands
|
54
|
+
value.format_thousands
|
55
|
+
end
|
56
|
+
end
|
18
57
|
end
|
19
58
|
|
20
|
-
class PositiveInteger <
|
59
|
+
class PositiveInteger < Integer
|
21
60
|
@regexp = /\A\d*[1-9]\d*\Z/
|
22
61
|
@error = 'must be a positive integer'
|
23
62
|
end
|
24
63
|
|
25
|
-
class UnsignedInteger <
|
64
|
+
class UnsignedInteger < Integer
|
26
65
|
@regexp = /\A\d+\Z/
|
27
66
|
@error = 'must be a positive integer or zero'
|
28
67
|
end
|
29
68
|
|
30
|
-
class NegativeInteger <
|
69
|
+
class NegativeInteger < Integer
|
31
70
|
@regexp = /\A\-\d*[1-9]\d*\Z/
|
32
71
|
@error = 'must be a negative integer'
|
33
72
|
end
|
34
73
|
|
35
|
-
class
|
74
|
+
class Decimal < Integer
|
75
|
+
@regexp = /\A\-?(\d+(\.\d*)?|\.\d+)\Z/
|
76
|
+
@error = 'must be a decimal number'
|
77
|
+
@default = 0
|
78
|
+
|
79
|
+
class << self
|
80
|
+
def parse(value)
|
81
|
+
value.to_f
|
82
|
+
end
|
83
|
+
|
84
|
+
def value_to_s(value)
|
85
|
+
# add commas for thousands, to integer part only
|
86
|
+
value.format_thousands
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
class AlphaNum < self
|
36
92
|
@regexp = /\A[a-z0-9]\Z/i
|
37
|
-
@error = '
|
93
|
+
@error = 'only letters and numbers allowed'
|
38
94
|
end
|
39
95
|
|
40
|
-
class Word
|
96
|
+
class Word < self
|
41
97
|
@regexp = /\A\w+\Z/
|
42
|
-
@error = '
|
98
|
+
@error = 'only letters, numbers, and underscores allowed'
|
43
99
|
end
|
44
100
|
|
45
|
-
class EmailAddress
|
101
|
+
class EmailAddress < self
|
46
102
|
@regexp = /\A[A-Z0-9._%+-]+\@([A-Z0-9-]+\.)+[A-Z]{2,4}\Z/i
|
47
103
|
@error = 'must be a valid email address'
|
104
|
+
|
105
|
+
def validate(value)
|
106
|
+
debug 'hi'
|
107
|
+
super(value)
|
108
|
+
end
|
48
109
|
end
|
49
110
|
|
50
|
-
class
|
111
|
+
class DateTime < self
|
112
|
+
@regexp = /\A\d+\D\d+(\D\d+)?\s+\d{1,2}\:\d{2}\s*[ap]m\Z/i
|
113
|
+
@error = 'must be a valid date and time'
|
114
|
+
@legend = 'm/d/yyyy h:mm a/pm'
|
115
|
+
@input_width = 140
|
116
|
+
@default = SequelZeroTime.new('0000-00-00 00:00:00')
|
117
|
+
|
118
|
+
class << self
|
119
|
+
def parse(value)
|
120
|
+
::Time.parse(value.gsub(/[-\.]/,'/'))
|
121
|
+
end
|
122
|
+
|
123
|
+
def value_to_s(value)
|
124
|
+
value.strftime("%m/%d/%Y %I:%M %p").gsub(/0(\d[\:\/])/,'\1')
|
125
|
+
end
|
126
|
+
|
127
|
+
def validate(value)
|
128
|
+
value =~ /\S/ ? super(value) : nil
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
class Date < DateTime
|
51
134
|
@regexp = /\A\d+\D\d+(\D\d+)?\Z/
|
52
135
|
@error = 'must be a valid date'
|
136
|
+
@legend = 'm/d/yyyy'
|
137
|
+
@input_width = 80
|
138
|
+
@default = SequelZeroTime.new('0000-00-00')
|
139
|
+
|
140
|
+
def self.value_to_s(value)
|
141
|
+
value.strftime("%m/%d/%Y")
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
class Time < DateTime
|
146
|
+
@regexp = /\A\d+\:\d+\s*[ap]m\Z/i,
|
147
|
+
@error = 'must be a valid time'
|
148
|
+
@legend = 'h:mm a/pm'
|
149
|
+
@input_width = 80
|
150
|
+
end
|
151
|
+
|
152
|
+
class MonthYear < Date
|
153
|
+
@regexp = /\A\d+\D\d+\Z/
|
154
|
+
@error = 'must be a valid month and year (m/yyyy)'
|
155
|
+
@legend = 'm/yyyy'
|
156
|
+
@input_width = 80
|
157
|
+
|
158
|
+
class << self
|
159
|
+
def parse(value)
|
160
|
+
month, year = value.sub(/\A\s*/,'').sub(/\s*\Z/,'').split(/\D+/)
|
161
|
+
# convert two-digit years to four-digit years
|
162
|
+
year = year.to_i
|
163
|
+
if year < 100
|
164
|
+
year += 1900
|
165
|
+
year += 100 if year < ::Time.now.year - 95
|
166
|
+
end
|
167
|
+
begin
|
168
|
+
::Time.parse("#{month}/#{year}")
|
169
|
+
rescue ArgumentError => e
|
170
|
+
raise Kiss::Format::ValidateError, e.message
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
def value_to_s(value)
|
175
|
+
value.strftime("%m/%Y")
|
176
|
+
end
|
177
|
+
end
|
53
178
|
end
|
54
179
|
|
55
180
|
|
56
|
-
@@
|
57
|
-
:integer =>
|
181
|
+
@@symbols = {
|
182
|
+
:integer => Integer,
|
183
|
+
|
184
|
+
:integer_positive => PositiveInteger,
|
185
|
+
:positive_integer => PositiveInteger,
|
186
|
+
:id => PositiveInteger,
|
187
|
+
|
188
|
+
:integer_unsigned => UnsignedInteger,
|
189
|
+
:unsigned_integer => UnsignedInteger,
|
190
|
+
:id_or_zero => UnsignedInteger,
|
191
|
+
:id_zero => UnsignedInteger,
|
192
|
+
|
193
|
+
:integer_negative => NegativeInteger,
|
194
|
+
:negative_integer => NegativeInteger,
|
58
195
|
|
59
|
-
:
|
60
|
-
:positive_integer => Kiss::Format::PositiveInteger,
|
61
|
-
:id => Kiss::Format::PositiveInteger,
|
196
|
+
:decimal => Decimal,
|
62
197
|
|
63
|
-
:
|
64
|
-
:
|
65
|
-
:id_or_zero => Kiss::Format::UnsignedInteger,
|
66
|
-
:id_zero => Kiss::Format::UnsignedInteger,
|
198
|
+
:word => Word,
|
199
|
+
:alphanum => AlphaNum,
|
67
200
|
|
68
|
-
:
|
69
|
-
:
|
201
|
+
:email => EmailAddress,
|
202
|
+
:email_address => EmailAddress,
|
70
203
|
|
71
|
-
:
|
72
|
-
:
|
73
|
-
:
|
74
|
-
:
|
204
|
+
:datetime => DateTime,
|
205
|
+
:date => Date,
|
206
|
+
:time => Time,
|
207
|
+
:month_year => MonthYear
|
75
208
|
}
|
76
|
-
def self.
|
77
|
-
|
209
|
+
def self.lookup(format)
|
210
|
+
format = format.to_sym if format.is_a?(String)
|
211
|
+
format.is_a?(Symbol) ?
|
212
|
+
# if symbol, lookup format by symbol
|
213
|
+
@@symbols[format] || self :
|
214
|
+
# else return format, or Kiss::Format if format not defined
|
215
|
+
format || self
|
78
216
|
end
|
79
217
|
end
|
80
218
|
end
|
data/lib/kiss/hacks.rb
CHANGED
@@ -1,14 +1,21 @@
|
|
1
1
|
# This is a collection of hacks to enable various Ruby lnaguage enhancements
|
2
2
|
# and other features of Kiss.
|
3
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
4
|
# Placeholder; to be overloaded by Kiss Rack builder option MultiBench
|
9
5
|
# (not yet implemented).
|
10
6
|
def multibench(*args); end
|
11
7
|
|
8
|
+
# This gets called outside of controller contexts; used mainly for debugging
|
9
|
+
# during Kiss framework development.
|
10
|
+
def debug(object)
|
11
|
+
print "Content-type: text/html\n\n" unless $debug
|
12
|
+
$debug = true
|
13
|
+
|
14
|
+
puts object.inspect + '<br/>'
|
15
|
+
puts '<small>at ' + Kernel.caller[0] + '</small><br/>'
|
16
|
+
object
|
17
|
+
end
|
18
|
+
|
12
19
|
class Kiss
|
13
20
|
# Used when Kiss#file_cache called from Kiss::Action#file_cache.
|
14
21
|
# Caught by Kiss's Rack builder option FileNotFound.
|
@@ -58,12 +65,30 @@ class Fixnum
|
|
58
65
|
def ago
|
59
66
|
Time.now - self
|
60
67
|
end
|
68
|
+
|
69
|
+
# format thousands
|
70
|
+
def format_thousands
|
71
|
+
to_s.reverse.gsub(/(\d{3})/,'\1,').sub(/\,(-?)$/,'\1').reverse
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
class BigDecimal
|
76
|
+
# Formats number with comma-separated thousands.
|
77
|
+
def format_thousands(value = to_f.to_s)
|
78
|
+
integer, decimal = value.split(/\./,2)
|
79
|
+
integer.reverse.gsub(/(\d{3})/,'\1,').sub(/\,(-?)$/,'\1').reverse + '.' + decimal
|
80
|
+
end
|
81
|
+
|
82
|
+
# Formats number to two decimal places.
|
83
|
+
def format_currency
|
84
|
+
format_thousands(sprintf("%0.2f",to_f))
|
85
|
+
end
|
61
86
|
end
|
62
87
|
|
63
88
|
class Date
|
64
89
|
# Returns string representing date in m/d/yyyy format
|
65
90
|
def mdy
|
66
|
-
|
91
|
+
strftime("%m/%d/%Y")
|
67
92
|
end
|
68
93
|
end
|
69
94
|
|
@@ -77,31 +102,117 @@ class NilClass
|
|
77
102
|
def length
|
78
103
|
0
|
79
104
|
end
|
105
|
+
# Ruby default: nil.id == 4 (now deprecated, but still present in 1.8)
|
106
|
+
# can cause unexpected results when using id from nil Sequel results
|
107
|
+
# so we'll raise an error instead, effectively undefining 'id' method
|
108
|
+
def id
|
109
|
+
raise NoMethodError, "undefined method `id' for nil:NilClass"
|
110
|
+
end
|
80
111
|
end
|
81
112
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
113
|
+
class SequelZeroTime < String
|
114
|
+
def initialize(value = '0000-00-00 00:00',*args,&block)
|
115
|
+
super(value,*args,&block)
|
116
|
+
end
|
117
|
+
|
118
|
+
# arithmetic operators
|
119
|
+
def +(*args)
|
120
|
+
self
|
121
|
+
end
|
122
|
+
def -(*args)
|
123
|
+
self
|
124
|
+
end
|
125
|
+
|
126
|
+
# comparision operators
|
127
|
+
def ==(value)
|
128
|
+
(value == 0 || value.is_a?(SequelZeroTime)) ? true : false
|
129
|
+
end
|
130
|
+
def >(*args)
|
131
|
+
return false
|
132
|
+
end
|
133
|
+
def >=(*args)
|
134
|
+
(value == 0 || value.is_a?(SequelZeroTime)) ? true : false
|
135
|
+
end
|
136
|
+
def <(*args)
|
137
|
+
return true
|
138
|
+
end
|
139
|
+
def <=(*args)
|
140
|
+
return true
|
141
|
+
end
|
142
|
+
|
143
|
+
# format conversion
|
144
|
+
def to_f
|
145
|
+
0
|
146
|
+
end
|
147
|
+
def to_i
|
148
|
+
0
|
149
|
+
end
|
150
|
+
def to_s
|
151
|
+
''
|
152
|
+
end
|
153
|
+
def strftime(*args)
|
154
|
+
''
|
155
|
+
end
|
156
|
+
def mdy
|
157
|
+
''
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
class Date
|
162
|
+
class << self
|
163
|
+
alias_method :old_parse, :parse
|
164
|
+
def parse(*args, &block)
|
165
|
+
return SequelZeroTime.new(args[0]) if args[0] =~ /0000/
|
166
|
+
old_parse(*args, &block)
|
89
167
|
end
|
90
168
|
end
|
169
|
+
|
170
|
+
# comparision operators
|
171
|
+
def ==(value)
|
172
|
+
(value == 0 || value.is_a?(SequelZeroTime)) ? false : super(value)
|
173
|
+
end
|
174
|
+
def >(value)
|
175
|
+
(value == 0 || value.is_a?(SequelZeroTime)) ? true : super(value)
|
176
|
+
end
|
177
|
+
def >=(value)
|
178
|
+
(value == 0 || value.is_a?(SequelZeroTime)) ? true : super(value)
|
179
|
+
end
|
180
|
+
def <(value)
|
181
|
+
(value == 0 || value.is_a?(SequelZeroTime)) ? false : super(value)
|
182
|
+
end
|
183
|
+
def <=(value)
|
184
|
+
(value == 0 || value.is_a?(SequelZeroTime)) ? false : super(value)
|
185
|
+
end
|
186
|
+
end
|
91
187
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
nil
|
99
|
-
else
|
100
|
-
raise Sequel::Error::InvalidValue, "Invalid date value '#{self}' (#{e.message})"
|
101
|
-
end
|
188
|
+
class Time
|
189
|
+
class << self
|
190
|
+
alias_method :old_parse, :parse
|
191
|
+
def parse(*args, &block)
|
192
|
+
return SequelZeroTime.new(args[0]) if args[0] =~ /0000/
|
193
|
+
old_parse(*args, &block)
|
102
194
|
end
|
103
195
|
end
|
104
196
|
|
197
|
+
# comparision operators
|
198
|
+
def ==(value)
|
199
|
+
(value == 0 || value.is_a?(SequelZeroTime)) ? false : super(value)
|
200
|
+
end
|
201
|
+
def >(value)
|
202
|
+
(value == 0 || value.is_a?(SequelZeroTime)) ? true : super(value)
|
203
|
+
end
|
204
|
+
def >=(value)
|
205
|
+
(value == 0 || value.is_a?(SequelZeroTime)) ? true : super(value)
|
206
|
+
end
|
207
|
+
def <(value)
|
208
|
+
(value == 0 || value.is_a?(SequelZeroTime)) ? false : super(value)
|
209
|
+
end
|
210
|
+
def <=(value)
|
211
|
+
(value == 0 || value.is_a?(SequelZeroTime)) ? false : super(value)
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
class String
|
105
216
|
def to_const
|
106
217
|
begin
|
107
218
|
parts = self.split(/::/)
|
data/lib/kiss/mailer.rb
CHANGED
@@ -2,17 +2,10 @@ class Kiss
|
|
2
2
|
# This class creates, renders, and sends email messages.
|
3
3
|
class Mailer
|
4
4
|
include Kiss::TemplateMethods
|
5
|
-
|
6
|
-
# Class Methods
|
7
|
-
|
8
|
-
def self.set_controller(controller)
|
9
|
-
@@controller = controller
|
10
|
-
end
|
11
|
-
|
12
|
-
# Instance Methods
|
13
5
|
|
14
6
|
# Creates new email message object.
|
15
|
-
def initialize(options = {})
|
7
|
+
def initialize(controller,options = {})
|
8
|
+
@controller = controller
|
16
9
|
@options = {
|
17
10
|
:engine => :sendmail
|
18
11
|
}.merge(options)
|
@@ -20,13 +13,8 @@ class Kiss
|
|
20
13
|
@data = {}
|
21
14
|
end
|
22
15
|
|
23
|
-
# Invokes controller's file_cache.
|
24
|
-
def file_cache(*args,&block)
|
25
|
-
controller.file_cache(*args,&block)
|
26
|
-
end
|
27
|
-
|
28
16
|
def controller
|
29
|
-
|
17
|
+
@controller
|
30
18
|
end
|
31
19
|
|
32
20
|
# Renders email template to string, unless message option is
|
@@ -36,7 +24,7 @@ class Kiss
|
|
36
24
|
|
37
25
|
unless @options[:message].is_a?(String)
|
38
26
|
if template_name = @options[:template]
|
39
|
-
@template_dir =
|
27
|
+
@template_dir = controller.email_template_dir
|
40
28
|
raise 'email_template_dir path not set' unless @template_dir
|
41
29
|
|
42
30
|
data = vars = @options[:data] || @options[:vars]
|