kiss 0.9.4 → 1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,80 +1,218 @@
1
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.
2
+ # This class is used to validate and parse strings into Sequel-ready values.
7
3
  class Format
8
- def self.regexp
9
- @@regexp
10
- end
11
- def self.error
12
- @@error
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 < Kiss::Format
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 < Kiss::Format
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 < Kiss::Format
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 < Kiss::Format
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 AlphaNum
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 = 'allows only letters and numbers'
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 = 'allows only letters, numbers, and _'
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 Date
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
- @@classes = {
57
- :integer => Kiss::Format::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
- :integer_positive => Kiss::Format::PositiveInteger,
60
- :positive_integer => Kiss::Format::PositiveInteger,
61
- :id => Kiss::Format::PositiveInteger,
196
+ :decimal => Decimal,
62
197
 
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,
198
+ :word => Word,
199
+ :alphanum => AlphaNum,
67
200
 
68
- :integer_negative => Kiss::Format::NegativeInteger,
69
- :negative_integer => Kiss::Format::NegativeInteger,
201
+ :email => EmailAddress,
202
+ :email_address => EmailAddress,
70
203
 
71
- :word => Kiss::Format::Word,
72
- :alphanum => Kiss::Format::AlphaNum,
73
- :email_address => Kiss::Format::EmailAddress,
74
- :date => Kiss::Format::Date
204
+ :datetime => DateTime,
205
+ :date => Date,
206
+ :time => Time,
207
+ :month_year => MonthYear
75
208
  }
76
- def self.from_symbol(symbol)
77
- return @@classes[symbol] || (raise "no format for #{symbol}")
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
@@ -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
- sprintf('%d/%d/%04d',month,mday,year)
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
- # 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})"
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
- # 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
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(/::/)
@@ -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
- @@controller
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 = @@controller.email_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]