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.
@@ -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]