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