class Kiss # This class is used to validate and parse strings into Sequel-ready values. class Format # 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 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 < Integer @regexp = /\A\d*[1-9]\d*\Z/ @error = 'must be a positive integer' end class UnsignedInteger < Integer @regexp = /\A\d+\Z/ @error = 'must be a positive integer or zero' end class NegativeInteger < Integer @regexp = /\A\-\d*[1-9]\d*\Z/ @error = 'must be a negative integer' end 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 = 'only letters and numbers allowed' end class Word < self @regexp = /\A\w+\Z/ @error = 'only letters, numbers, and underscores allowed' end 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 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 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 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 def value_to_s(value) value.strftime("%m/%Y") end end end @@symbols = { :integer => Integer, :integer_positive => PositiveInteger, :positive_integer => PositiveInteger, :id => PositiveInteger, :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.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