added compile_set method to get expressions and combine date and datetime expressions to allow a date string to parse when type is datetime

made internal methods private
This commit is contained in:
Adam Meehan 2008-07-23 12:39:20 +10:00
parent 85ac2bfc69
commit 7500781887
2 changed files with 97 additions and 70 deletions

View File

@ -83,15 +83,15 @@ module ValidatesTimeliness
]
@@datetime_formats = [
'yyyy-mm-dd hh:nn:ss.u',
'yyyy-mm-dd hh:nn:ss',
'yyyy-mm-dd h:nn',
'yyyy-mm-dd hh:nn:ss.u',
'm/d/yy h:nn:ss',
'm/d/yy h:nn',
'm/d/yy h:nn_ampm',
'm/d/yy h:nn',
'd/m/yy hh:nn:ss',
'd/m/yy h:nn',
'd/m/yy h:nn_ampm',
'd/m/yy h:nn',
'ddd, dd mmm yyyy hh:nn:ss (zo|tz)', # RFC 822
'ddd mmm d hh:nn:ss zo yyyy', # Ruby time string
'yyyy-mm-ddThh:nn:ss(?:Z|zo)' # iso 8601
@ -151,51 +151,6 @@ module ValidatesTimeliness
class << self
# Compile formats into validation regexps and format procs
def format_expression_generator(string_format)
regexp = string_format.dup
order = {}
regexp.gsub!(/([\.\\])/, '\\\\\1') # escapes dots and backslashes ]/
format_tokens.each do |token|
token_name = token.keys.first
token_regexp, regexp_str, arg_key = *token.values.first
# hack for lack of look-behinds. If has a capture group then is
# considered an anchor to put straight back in the regexp string.
regexp.gsub!(token_regexp) {|m| "#{$1}" + regexp_str }
order[arg_key] = $~.begin(0) if $~ && !arg_key.nil?
end
return Regexp.new(regexp), format_proc(order)
rescue
puts "The following format regular expression failed to compile: #{regexp}\n from format #{string_format}."
raise
end
# Generates a proc which when executed maps the regexp capture groups to a
# proc argument based on order captured. A time array is built using the proc
# argument in the position indicated by the first element of the proc arg
# array.
#
# Examples:
#
# 'yyyy-mm-dd hh:nn' => lambda {|y,m,d,h,n| md||=0; [unambiguous_year(y),month_index(m),d,full_hour(h,md),n,nil,nil].map {|i| i.to_i } }
# 'dd/mm/yyyy h:nn_ampm' => lambda {|d,m,y,h,n,md| md||=0; [unambiguous_year(y),month_index(m),d,full_hour(h,md),n,nil,nil].map {|i| i.to_i } }
#
def format_proc(order)
arg_map = format_proc_args
args = order.invert.sort.map {|p| arg_map[p[1]][1] }
arr = [nil] * 7
order.keys.each {|k| i = arg_map[k][0]; arr[i] = arg_map[k][2] unless i.nil? }
proc_string = "lambda {|#{args.join(',')}| md||=nil; [#{arr.map {|i| i.nil? ? 'nil' : i }.join(',')}].map {|i| i.to_i } }"
eval proc_string
end
def compile_formats(formats)
formats.collect { |format| regexp, format_proc = format_expression_generator(format) }
end
def compile_format_expressions
@@time_expressions = compile_formats(@@time_formats)
@@date_expressions = compile_formats(@@date_formats)
@ -206,12 +161,14 @@ module ValidatesTimeliness
# pre or post match strings to exist if strict is false. Otherwise wrap
# regexp in start and end anchors.
# Returns 7 part time array.
def parse(time_string, type, strict=true)
expressions = self.send("#{type}_expressions")
def parse(string, type, strict=true)
return string unless string.is_a?(String)
expressions = expression_set(type, string)
time_array = nil
expressions.each do |(regexp, processor)|
regexp = strict || type == :datetime ? /\A#{regexp}\Z/ : (type == :date ? /\A#{regexp}/ : /#{regexp}\Z/)
if matches = regexp.match(time_string.strip)
if matches = regexp.match(string.strip)
time_array = processor.call(*matches[1..7])
break
end
@ -260,6 +217,71 @@ module ValidatesTimeliness
compile_format_expressions
end
private
# Compile formats into validation regexps and format procs
def format_expression_generator(string_format)
regexp = string_format.dup
order = {}
regexp.gsub!(/([\.\\])/, '\\\\\1') # escapes dots and backslashes ]/
format_tokens.each do |token|
token_name = token.keys.first
token_regexp, regexp_str, arg_key = *token.values.first
# hack for lack of look-behinds. If has a capture group then is
# considered an anchor to put straight back in the regexp string.
regexp.gsub!(token_regexp) {|m| "#{$1}" + regexp_str }
order[arg_key] = $~.begin(0) if $~ && !arg_key.nil?
end
return Regexp.new(regexp), format_proc(order)
rescue
puts "The following format regular expression failed to compile: #{regexp}\n from format #{string_format}."
raise
end
# Generates a proc which when executed maps the regexp capture groups to a
# proc argument based on order captured. A time array is built using the proc
# argument in the position indicated by the first element of the proc arg
# array.
#
# Examples:
#
# 'yyyy-mm-dd hh:nn' => lambda {|y,m,d,h,n| md||=0; [unambiguous_year(y),month_index(m),d,full_hour(h,md),n,nil,nil].map {|i| i.to_i } }
# 'dd/mm/yyyy h:nn_ampm' => lambda {|d,m,y,h,n,md| md||=0; [unambiguous_year(y),month_index(m),d,full_hour(h,md),n,nil,nil].map {|i| i.to_i } }
#
def format_proc(order)
arg_map = format_proc_args
args = order.invert.sort.map {|p| arg_map[p[1]][1] }
arr = [nil] * 7
order.keys.each {|k| i = arg_map[k][0]; arr[i] = arg_map[k][2] unless i.nil? }
proc_string = "lambda {|#{args.join(',')}| md||=nil; [#{arr.map {|i| i.nil? ? 'nil' : i }.join(',')}].map {|i| i.to_i } }"
eval proc_string
end
def compile_formats(formats)
formats.collect { |format| regexp, format_proc = format_expression_generator(format) }
end
# Pick expression set and combine date and datetimes for
# datetime attributes to allow date string as datetime
def expression_set(type, string)
case type
when :date
date_expressions
when :time
time_expressions
when :datetime
# gives a speed-up for date string as datetime attributes
if string.length < 11
date_expressions + datetime_expressions
else
datetime_expressions + date_expressions
end
end
end
def full_hour(hour, meridian)
hour = hour.to_i
return hour if meridian.nil?

View File

@ -153,6 +153,11 @@ describe ValidatesTimeliness::Formats do
time_array.should == [2000,2,1,12,13,14,0]
end
it "should parse date string when type is datetime" do
time_array = formats.parse('2000-02-01', :datetime, false)
time_array.should == [2000,2,1,0,0,0,0]
end
it "should ignore time when extracting date and strict is false" do
time_array = formats.parse('2000-02-01 12:12', :date, false)
time_array.should == [2000,2,1,0,0,0,0]
@ -252,15 +257,15 @@ describe ValidatesTimeliness::Formats do
def generate_regexp(format)
# wrap in line start and end anchors to emulate extract values method
/\A#{formats.format_expression_generator(format)[0]}\Z/
/\A#{formats.send(:format_expression_generator, format)[0]}\Z/
end
def generate_regexp_str(format)
formats.format_expression_generator(format)[0].inspect
formats.send(:format_expression_generator, format)[0].inspect
end
def generate_proc(format)
formats.format_expression_generator(format)[1]
formats.send(:format_expression_generator, format)[1]
end
def delete_format(type, format)