lib/kiss/format.rb in kiss-0.9.4 vs lib/kiss/format.rb in kiss-1.0
- old
+ new
@@ -1,80 +1,218 @@
class Kiss
- # This class leftover from a previous version of Kiss and is not currently used.
- # In fact, this file is not required from any other part of Kiss.
- #
- # But we're keeping it around in case it's useful for the anticipated rewrite of
- # Kiss.validate_format.
+ # This class is used to validate and parse strings into Sequel-ready values.
class Format
- def self.regexp
- @@regexp
+ # used to raise exceptions when values don't validate to format regexp
+ class ValidateError < Exception; end
+
+ @regexp = //
+ @error = 'invalid format'
+ @legend = nil
+ @input_width = nil
+ @default = ''
+
+ class << self
+ attr_accessor :regexp, :error, :legend, :input_width, :default
+
+ # copy Format class instance variables to their subclasses
+ def inherited(subclass)
+ self.instance_variables.each do |var|
+ subclass.instance_variable_set(var, instance_variable_get(var))
+ end
+ end
+
+ def parse(value)
+ value
+ end
+
+ def value_to_s(value)
+ value.to_s
+ end
+
+ def validate(value)
+ raise Kiss::Format::ValidateError, @error unless value.to_s =~ @regexp
+ parse(value)
+ end
end
- def self.error
- @@error
- end
- class Integer < Kiss::Format
+ class Integer < self
@regexp = /\A\-?\d+\Z/
@error = 'must be an integer'
+ @default = 0
+
+ class << self
+ def parse(value)
+ value.to_i
+ end
+
+ def validate(value)
+ # remove commas for thousands
+ value =~ /\d/ ? super(value.gsub(/,/,'')) : nil
+ end
+
+ def value_to_s(value)
+ # add commas for thousands
+ value.format_thousands
+ end
+ end
end
- class PositiveInteger < Kiss::Format
+ class PositiveInteger < Integer
@regexp = /\A\d*[1-9]\d*\Z/
@error = 'must be a positive integer'
end
- class UnsignedInteger < Kiss::Format
+ class UnsignedInteger < Integer
@regexp = /\A\d+\Z/
@error = 'must be a positive integer or zero'
end
- class NegativeInteger < Kiss::Format
+ class NegativeInteger < Integer
@regexp = /\A\-\d*[1-9]\d*\Z/
@error = 'must be a negative integer'
end
- class AlphaNum
+ class Decimal < Integer
+ @regexp = /\A\-?(\d+(\.\d*)?|\.\d+)\Z/
+ @error = 'must be a decimal number'
+ @default = 0
+
+ class << self
+ def parse(value)
+ value.to_f
+ end
+
+ def value_to_s(value)
+ # add commas for thousands, to integer part only
+ value.format_thousands
+ end
+ end
+ end
+
+ class AlphaNum < self
@regexp = /\A[a-z0-9]\Z/i
- @error = 'allows only letters and numbers'
+ @error = 'only letters and numbers allowed'
end
- class Word
+ class Word < self
@regexp = /\A\w+\Z/
- @error = 'allows only letters, numbers, and _'
+ @error = 'only letters, numbers, and underscores allowed'
end
- class EmailAddress
+ class EmailAddress < self
@regexp = /\A[A-Z0-9._%+-]+\@([A-Z0-9-]+\.)+[A-Z]{2,4}\Z/i
@error = 'must be a valid email address'
+
+ def validate(value)
+ debug 'hi'
+ super(value)
+ end
end
- class Date
+ class DateTime < self
+ @regexp = /\A\d+\D\d+(\D\d+)?\s+\d{1,2}\:\d{2}\s*[ap]m\Z/i
+ @error = 'must be a valid date and time'
+ @legend = 'm/d/yyyy h:mm a/pm'
+ @input_width = 140
+ @default = SequelZeroTime.new('0000-00-00 00:00:00')
+
+ class << self
+ def parse(value)
+ ::Time.parse(value.gsub(/[-\.]/,'/'))
+ end
+
+ def value_to_s(value)
+ value.strftime("%m/%d/%Y %I:%M %p").gsub(/0(\d[\:\/])/,'\1')
+ end
+
+ def validate(value)
+ value =~ /\S/ ? super(value) : nil
+ end
+ end
+ end
+
+ class Date < DateTime
@regexp = /\A\d+\D\d+(\D\d+)?\Z/
@error = 'must be a valid date'
+ @legend = 'm/d/yyyy'
+ @input_width = 80
+ @default = SequelZeroTime.new('0000-00-00')
+
+ def self.value_to_s(value)
+ value.strftime("%m/%d/%Y")
+ end
end
+ class Time < DateTime
+ @regexp = /\A\d+\:\d+\s*[ap]m\Z/i,
+ @error = 'must be a valid time'
+ @legend = 'h:mm a/pm'
+ @input_width = 80
+ end
- @@classes = {
- :integer => Kiss::Format::Integer,
+ class MonthYear < Date
+ @regexp = /\A\d+\D\d+\Z/
+ @error = 'must be a valid month and year (m/yyyy)'
+ @legend = 'm/yyyy'
+ @input_width = 80
- :integer_positive => Kiss::Format::PositiveInteger,
- :positive_integer => Kiss::Format::PositiveInteger,
- :id => Kiss::Format::PositiveInteger,
+ class << self
+ def parse(value)
+ month, year = value.sub(/\A\s*/,'').sub(/\s*\Z/,'').split(/\D+/)
+ # convert two-digit years to four-digit years
+ year = year.to_i
+ if year < 100
+ year += 1900
+ year += 100 if year < ::Time.now.year - 95
+ end
+ begin
+ ::Time.parse("#{month}/#{year}")
+ rescue ArgumentError => e
+ raise Kiss::Format::ValidateError, e.message
+ end
+ end
- :integer_unsigned => Kiss::Format::UnsignedInteger,
- :unsigned_integer => Kiss::Format::UnsignedInteger,
- :id_or_zero => Kiss::Format::UnsignedInteger,
- :id_zero => Kiss::Format::UnsignedInteger,
+ def value_to_s(value)
+ value.strftime("%m/%Y")
+ end
+ end
+ end
+
+
+ @@symbols = {
+ :integer => Integer,
- :integer_negative => Kiss::Format::NegativeInteger,
- :negative_integer => Kiss::Format::NegativeInteger,
+ :integer_positive => PositiveInteger,
+ :positive_integer => PositiveInteger,
+ :id => PositiveInteger,
- :word => Kiss::Format::Word,
- :alphanum => Kiss::Format::AlphaNum,
- :email_address => Kiss::Format::EmailAddress,
- :date => Kiss::Format::Date
+ :integer_unsigned => UnsignedInteger,
+ :unsigned_integer => UnsignedInteger,
+ :id_or_zero => UnsignedInteger,
+ :id_zero => UnsignedInteger,
+
+ :integer_negative => NegativeInteger,
+ :negative_integer => NegativeInteger,
+
+ :decimal => Decimal,
+
+ :word => Word,
+ :alphanum => AlphaNum,
+
+ :email => EmailAddress,
+ :email_address => EmailAddress,
+
+ :datetime => DateTime,
+ :date => Date,
+ :time => Time,
+ :month_year => MonthYear
}
- def self.from_symbol(symbol)
- return @@classes[symbol] || (raise "no format for #{symbol}")
+ def self.lookup(format)
+ format = format.to_sym if format.is_a?(String)
+ format.is_a?(Symbol) ?
+ # if symbol, lookup format by symbol
+ @@symbols[format] || self :
+ # else return format, or Kiss::Format if format not defined
+ format || self
end
end
end
\ No newline at end of file