validates_timeliness 2.3.2 → 3.0.0.beta

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. data/CHANGELOG +12 -4
  2. data/LICENSE +1 -1
  3. data/README.rdoc +138 -280
  4. data/Rakefile +30 -16
  5. data/lib/generators/validates_timeliness/install_generator.rb +17 -0
  6. data/lib/generators/validates_timeliness/templates/en.yml +16 -0
  7. data/lib/generators/validates_timeliness/templates/validates_timeliness.rb +22 -0
  8. data/lib/validates_timeliness.rb +46 -52
  9. data/lib/validates_timeliness/attribute_methods.rb +51 -0
  10. data/lib/validates_timeliness/conversion.rb +69 -0
  11. data/lib/validates_timeliness/extensions.rb +14 -0
  12. data/lib/validates_timeliness/extensions/date_time_select.rb +45 -0
  13. data/lib/validates_timeliness/extensions/multiparameter_handler.rb +31 -0
  14. data/lib/validates_timeliness/helper_methods.rb +41 -0
  15. data/lib/validates_timeliness/orms/active_record.rb +14 -0
  16. data/lib/validates_timeliness/parser.rb +389 -17
  17. data/lib/validates_timeliness/validator.rb +37 -200
  18. data/lib/validates_timeliness/version.rb +1 -1
  19. data/spec/model_helpers.rb +27 -0
  20. data/spec/spec_helper.rb +74 -43
  21. data/spec/test_model.rb +56 -0
  22. data/spec/validates_timeliness/attribute_methods_spec.rb +36 -0
  23. data/spec/validates_timeliness/conversion_spec.rb +204 -0
  24. data/spec/validates_timeliness/extensions/date_time_select_spec.rb +178 -0
  25. data/spec/validates_timeliness/extensions/multiparameter_handler_spec.rb +21 -0
  26. data/spec/validates_timeliness/helper_methods_spec.rb +36 -0
  27. data/spec/{formats_spec.rb → validates_timeliness/parser_spec.rb} +105 -71
  28. data/spec/validates_timeliness/validator/after_spec.rb +59 -0
  29. data/spec/validates_timeliness/validator/before_spec.rb +59 -0
  30. data/spec/validates_timeliness/validator/is_at_spec.rb +63 -0
  31. data/spec/validates_timeliness/validator/on_or_after_spec.rb +59 -0
  32. data/spec/validates_timeliness/validator/on_or_before_spec.rb +59 -0
  33. data/spec/validates_timeliness/validator_spec.rb +172 -0
  34. data/validates_timeliness.gemspec +30 -0
  35. metadata +42 -40
  36. data/TODO +0 -8
  37. data/lib/validates_timeliness/action_view/instance_tag.rb +0 -52
  38. data/lib/validates_timeliness/active_record/attribute_methods.rb +0 -77
  39. data/lib/validates_timeliness/active_record/multiparameter_attributes.rb +0 -69
  40. data/lib/validates_timeliness/formats.rb +0 -368
  41. data/lib/validates_timeliness/locale/en.new.yml +0 -18
  42. data/lib/validates_timeliness/locale/en.old.yml +0 -18
  43. data/lib/validates_timeliness/matcher.rb +0 -1
  44. data/lib/validates_timeliness/spec/rails/matchers/validate_timeliness.rb +0 -162
  45. data/lib/validates_timeliness/validation_methods.rb +0 -46
  46. data/spec/action_view/instance_tag_spec.rb +0 -194
  47. data/spec/active_record/attribute_methods_spec.rb +0 -157
  48. data/spec/active_record/multiparameter_attributes_spec.rb +0 -118
  49. data/spec/ginger_scenarios.rb +0 -19
  50. data/spec/parser_spec.rb +0 -65
  51. data/spec/resources/application.rb +0 -2
  52. data/spec/resources/person.rb +0 -3
  53. data/spec/resources/schema.rb +0 -10
  54. data/spec/resources/sqlite_patch.rb +0 -19
  55. data/spec/spec/rails/matchers/validate_timeliness_spec.rb +0 -245
  56. data/spec/time_travel/MIT-LICENSE +0 -20
  57. data/spec/time_travel/time_extensions.rb +0 -33
  58. data/spec/time_travel/time_travel.rb +0 -12
  59. data/spec/validator_spec.rb +0 -723
data/TODO DELETED
@@ -1,8 +0,0 @@
1
- - valid formats could come from locale file
2
- - add replace_formats instead add_formats :before
3
- - array of values for all temporal options
4
- - use tz and zo value from time string?
5
- - filter valid formats rather than remove for hot swapping without recompilation
6
- - config generator
7
- - move all config into top namespace
8
- - remove action_view stuff
@@ -1,52 +0,0 @@
1
- # TODO remove this from the plugin for v3.
2
- module ValidatesTimeliness
3
-
4
- def self.enable_datetime_select_invalid_value_extension!
5
- ::ActionView::Helpers::InstanceTag.send(:include, ValidatesTimeliness::ActionView::InstanceTag)
6
- end
7
-
8
- module ActionView
9
-
10
- # Intercepts the date and time select helpers to reuse the values from the
11
- # the params rather than the parsed value. This allows invalid date/time
12
- # values to be redisplayed instead of blanks to aid correction by the user.
13
- # Its a minor usability improvement which is rarely an issue for the user.
14
- #
15
- module InstanceTag
16
-
17
- def self.included(base)
18
- selector_method = Rails::VERSION::STRING.to_f < 2.2 ? :date_or_time_select : :datetime_selector
19
- base.class_eval do
20
- alias_method :datetime_selector_without_timeliness, selector_method
21
- alias_method selector_method, :datetime_selector_with_timeliness
22
- end
23
- base.alias_method_chain :value, :timeliness
24
- end
25
-
26
- TimelinessDateTime = Struct.new(:year, :month, :day, :hour, :min, :sec)
27
-
28
- def datetime_selector_with_timeliness(*args)
29
- @timeliness_date_or_time_tag = true
30
- datetime_selector_without_timeliness(*args)
31
- end
32
-
33
- def value_with_timeliness(object)
34
- unless @timeliness_date_or_time_tag && @template_object.params[@object_name]
35
- return value_without_timeliness(object)
36
- end
37
-
38
- pairs = @template_object.params[@object_name].select {|k,v| k =~ /^#{@method_name}\(/ }
39
- return value_without_timeliness(object) if pairs.empty?
40
-
41
- values = pairs.map do |(param, value)|
42
- position = param.scan(/\(([0-9]*).*\)/).first.first
43
- [position, value]
44
- end.sort {|a,b| a[0] <=> b[0] }.map {|v| v[1] }
45
-
46
- TimelinessDateTime.new(*values)
47
- end
48
-
49
- end
50
-
51
- end
52
- end
@@ -1,77 +0,0 @@
1
- module ValidatesTimeliness
2
-
3
- def self.enable_active_record_datetime_parser!
4
- ::ActiveRecord::Base.send(:include, ValidatesTimeliness::ActiveRecord::AttributeMethods)
5
- end
6
-
7
- module ActiveRecord
8
-
9
- # Overrides write method for date, time and datetime columns
10
- # to use plugin parser. Also adds mechanism to store value
11
- # before type cast.
12
- #
13
- module AttributeMethods
14
-
15
- def self.included(base)
16
- base.extend ClassMethods
17
- base.class_eval do
18
- alias_method_chain :read_attribute_before_type_cast, :timeliness
19
- class << self
20
- alias_method_chain :define_attribute_methods, :timeliness
21
- end
22
- end
23
- end
24
-
25
- def write_date_time_attribute(attr_name, value, type, time_zone_aware)
26
- @attributes_cache["_#{attr_name}_before_type_cast"] = value
27
- value = ValidatesTimeliness::Parser.parse(value, type)
28
-
29
- if value && type != :date
30
- value = value.to_time
31
- value = value.in_time_zone if time_zone_aware
32
- end
33
-
34
- write_attribute(attr_name.to_sym, value)
35
- end
36
-
37
- def read_attribute_before_type_cast_with_timeliness(attr_name)
38
- cached_attr = "_#{attr_name}_before_type_cast"
39
- return @attributes_cache[cached_attr] if @attributes_cache.has_key?(cached_attr)
40
- read_attribute_before_type_cast_without_timeliness(attr_name)
41
- end
42
-
43
- module ClassMethods
44
-
45
- def define_attribute_methods_with_timeliness
46
- return if generated_methods?
47
- timeliness_methods = []
48
-
49
- columns_hash.each do |name, column|
50
- if [:date, :time, :datetime].include?(column.type)
51
- method_name = "#{name}="
52
- next if instance_method_already_implemented?(method_name)
53
-
54
- time_zone_aware = create_time_zone_conversion_attribute?(name, column) rescue false
55
- define_method(method_name) do |value|
56
- write_date_time_attribute(name, value, column.type, time_zone_aware)
57
- end
58
- timeliness_methods << method_name
59
- end
60
- end
61
-
62
- # Hack to get around instance_method_already_implemented? caching the
63
- # methods in the ivar. It then appears to subsequent calls that the
64
- # methods defined here, have not been and get defined again.
65
- @_defined_class_methods = nil
66
-
67
- define_attribute_methods_without_timeliness
68
- # add generated methods which is a Set object hence no += method
69
- timeliness_methods.each {|attr| generated_methods << attr }
70
- end
71
-
72
- end
73
-
74
- end
75
-
76
- end
77
- end
@@ -1,69 +0,0 @@
1
- module ValidatesTimeliness
2
-
3
- def self.enable_multiparameter_attributes_extension!
4
- ::ActiveRecord::Base.send(:include, ValidatesTimeliness::ActiveRecord::MultiparameterAttributes)
5
- end
6
-
7
- module ActiveRecord
8
-
9
- class << self
10
-
11
- def time_array_to_string(values, type)
12
- values.collect! {|v| v.to_s }
13
-
14
- case type
15
- when :date
16
- extract_date_from_multiparameter_attributes(values)
17
- when :time
18
- extract_time_from_multiparameter_attributes(values)
19
- when :datetime
20
- extract_date_from_multiparameter_attributes(values) + " " + extract_time_from_multiparameter_attributes(values)
21
- end
22
- end
23
-
24
- def extract_date_from_multiparameter_attributes(values)
25
- year = values[0].blank? ? nil : ValidatesTimeliness::Formats.unambiguous_year(values[0].rjust(2, "0"))
26
- [year, *values.slice(1, 2).map { |s| s.blank? ? nil : s.rjust(2, "0") }].join("-")
27
- end
28
-
29
- def extract_time_from_multiparameter_attributes(values)
30
- values[3..5].map { |s| s.blank? ? nil : s.rjust(2, "0") }.join(":")
31
- end
32
-
33
- end
34
-
35
- module MultiparameterAttributes
36
-
37
- def self.included(base)
38
- base.alias_method_chain :execute_callstack_for_multiparameter_attributes, :timeliness
39
- end
40
-
41
- # Assign dates and times as formatted strings to force the use of the plugin parser
42
- def execute_callstack_for_multiparameter_attributes_with_timeliness(callstack)
43
- errors = []
44
- callstack.each do |name, values|
45
- column = column_for_attribute(name)
46
- if column && [:date, :time, :datetime].include?(column.type)
47
- begin
48
- callstack.delete(name)
49
- if values.empty? || values.all?(&:nil?)
50
- send("#{name}=", nil)
51
- else
52
- value = ValidatesTimeliness::ActiveRecord.time_array_to_string(values, column.type)
53
- send("#{name}=", value)
54
- end
55
- rescue => ex
56
- errors << ::ActiveRecord::AttributeAssignmentError.new("error on assignment #{values.inspect} to #{name}", ex, name)
57
- end
58
- end
59
- end
60
- unless errors.empty?
61
- raise ::ActiveRecord::MultiparameterAssignmentErrors.new(errors), "#{errors.size} error(s) on assignment of multiparameter attributes"
62
- end
63
- execute_callstack_for_multiparameter_attributes_without_timeliness(callstack)
64
- end
65
-
66
- end
67
-
68
- end
69
- end
@@ -1,368 +0,0 @@
1
- require 'date'
2
-
3
- module ValidatesTimeliness
4
-
5
- # A date and time parsing library which allows you to add custom formats using
6
- # simple predefined tokens. This makes it much easier to catalogue and customize
7
- # the formats rather than dealing directly with regular expressions.
8
- #
9
- # Formats can be added or removed to customize the set of valid date or time
10
- # string values.
11
- #
12
- class Formats
13
- cattr_accessor :time_formats,
14
- :date_formats,
15
- :datetime_formats,
16
- :time_expressions,
17
- :date_expressions,
18
- :datetime_expressions,
19
- :format_tokens,
20
- :format_proc_args
21
-
22
-
23
- # Set the threshold value for a two digit year to be considered last century
24
- #
25
- # Default: 30
26
- #
27
- # Example:
28
- # year = '29' is considered 2029
29
- # year = '30' is considered 1930
30
- #
31
- cattr_accessor :ambiguous_year_threshold
32
- self.ambiguous_year_threshold = 30
33
-
34
- # Set the dummy date part for a time type value. Should be an array of 3 values
35
- # being year, month and day in that order.
36
- #
37
- # Default: [ 2000, 1, 1 ] same as ActiveRecord
38
- #
39
- cattr_accessor :dummy_date_for_time_type
40
- self.dummy_date_for_time_type = [ 2000, 1, 1 ]
41
-
42
- # Format tokens:
43
- # y = year
44
- # m = month
45
- # d = day
46
- # h = hour
47
- # n = minute
48
- # s = second
49
- # u = micro-seconds
50
- # ampm = meridian (am or pm) with or without dots (e.g. am, a.m, or a.m.)
51
- # _ = optional space
52
- # tz = Timezone abbreviation (e.g. UTC, GMT, PST, EST)
53
- # zo = Timezone offset (e.g. +10:00, -08:00, +1000)
54
- #
55
- # All other characters are considered literal. You can embed regexp in the
56
- # format but no gurantees that it will remain intact. If you avoid the use
57
- # of any token characters and regexp dots or backslashes as special characters
58
- # in the regexp, it may well work as expected. For special characters use
59
- # POSIX character clsses for safety.
60
- #
61
- # Repeating tokens:
62
- # x = 1 or 2 digits for unit (e.g. 'h' means an hour can be '9' or '09')
63
- # xx = 2 digits exactly for unit (e.g. 'hh' means an hour can only be '09')
64
- #
65
- # Special Cases:
66
- # yy = 2 or 4 digit year
67
- # yyyy = exactly 4 digit year
68
- # mmm = month long name (e.g. 'Jul' or 'July')
69
- # ddd = Day name of 3 to 9 letters (e.g. Wed or Wednesday)
70
- # u = microseconds matches 1 to 6 digits
71
- #
72
- # Any other invalid combination of repeating tokens will be swallowed up
73
- # by the next lowest length valid repeating token (e.g. yyy will be
74
- # replaced with yy)
75
-
76
- @@time_formats = [
77
- 'hh:nn:ss',
78
- 'hh-nn-ss',
79
- 'h:nn',
80
- 'h.nn',
81
- 'h nn',
82
- 'h-nn',
83
- 'h:nn_ampm',
84
- 'h.nn_ampm',
85
- 'h nn_ampm',
86
- 'h-nn_ampm',
87
- 'h_ampm'
88
- ]
89
-
90
- @@date_formats = [
91
- 'yyyy-mm-dd',
92
- 'yyyy/mm/dd',
93
- 'yyyy.mm.dd',
94
- 'm/d/yy',
95
- 'd/m/yy',
96
- 'm\d\yy',
97
- 'd\m\yy',
98
- 'd-m-yy',
99
- 'd.m.yy',
100
- 'd mmm yy'
101
- ]
102
-
103
- @@datetime_formats = [
104
- 'yyyy-mm-dd hh:nn:ss',
105
- 'yyyy-mm-dd h:nn',
106
- 'yyyy-mm-dd h:nn_ampm',
107
- 'yyyy-mm-dd hh:nn:ss.u',
108
- 'm/d/yy h:nn:ss',
109
- 'm/d/yy h:nn_ampm',
110
- 'm/d/yy h:nn',
111
- 'd/m/yy hh:nn:ss',
112
- 'd/m/yy h:nn_ampm',
113
- 'd/m/yy h:nn',
114
- 'ddd, dd mmm yyyy hh:nn:ss (zo|tz)', # RFC 822
115
- 'ddd mmm d hh:nn:ss zo yyyy', # Ruby time string
116
- 'yyyy-mm-ddThh:nn:ssZ', # iso 8601 without zone offset
117
- 'yyyy-mm-ddThh:nn:sszo' # iso 8601 with zone offset
118
- ]
119
-
120
-
121
- # All tokens available for format construction. The token array is made of
122
- # token regexp, validation regexp and key for format proc mapping if any.
123
- # If the token needs no format proc arg then the validation regexp should
124
- # not have a capturing group, as all captured groups are passed to the
125
- # format proc.
126
- #
127
- # The token regexp should only use a capture group if 'look-behind' anchor
128
- # is required. The first capture group will be considered a literal and put
129
- # into the validation regexp string as-is. This is a hack.
130
- @@format_tokens = [
131
- { 'd' => [ /(\A|[^d])d{1}(?=[^d])/, '(\d{1,2})', :day ] }, #/
132
- { 'ddd' => [ /d{3,}/, '(\w{3,9})' ] },
133
- { 'dd' => [ /d{2,}/, '(\d{2})', :day ] },
134
- { 'mmm' => [ /m{3,}/, '(\w{3,9})', :month ] },
135
- { 'mm' => [ /m{2}/, '(\d{2})', :month ] },
136
- { 'm' => [ /(\A|[^ap])m{1}/, '(\d{1,2})', :month ] },
137
- { 'yyyy' => [ /y{4,}/, '(\d{4})', :year ] },
138
- { 'yy' => [ /y{2,}/, '(\d{4}|\d{2})', :year ] },
139
- { 'hh' => [ /h{2,}/, '(\d{2})', :hour ] },
140
- { 'h' => [ /h{1}/, '(\d{1,2})', :hour ] },
141
- { 'nn' => [ /n{2,}/, '(\d{2})', :min ] },
142
- { 'n' => [ /n{1}/, '(\d{1,2})', :min ] },
143
- { 'ss' => [ /s{2,}/, '(\d{2})', :sec ] },
144
- { 's' => [ /s{1}/, '(\d{1,2})', :sec ] },
145
- { 'u' => [ /u{1,}/, '(\d{1,6})', :usec ] },
146
- { 'ampm' => [ /ampm/, '((?:[aApP])\.?[mM]\.?)', :meridian ] },
147
- { 'zo' => [ /zo/, '([+-]\d{2}:?\d{2})', :offset ] },
148
- { 'tz' => [ /tz/, '(?:[A-Z]{1,4})' ] },
149
- { '_' => [ /_/, '\s?' ] }
150
- ]
151
-
152
- # Arguments which will be passed to the format proc if matched in the
153
- # time string. The key must be the key from the format tokens. The array
154
- # consists of the arry position of the arg, the arg name, and the code to
155
- # place in the time array slot. The position can be nil which means the arg
156
- # won't be placed in the array.
157
- #
158
- # The code can be used to manipulate the arg value if required, otherwise
159
- # should just be the arg name.
160
- #
161
- @@format_proc_args = {
162
- :year => [0, 'y', 'unambiguous_year(y)'],
163
- :month => [1, 'm', 'month_index(m)'],
164
- :day => [2, 'd', 'd'],
165
- :hour => [3, 'h', 'full_hour(h,md)'],
166
- :min => [4, 'n', 'n'],
167
- :sec => [5, 's', 's'],
168
- :usec => [6, 'u', 'microseconds(u)'],
169
- :offset => [7, 'z', 'offset_in_seconds(z)'],
170
- :meridian => [nil, 'md', nil]
171
- }
172
-
173
- class << self
174
-
175
- def compile_format_expressions
176
- @@time_expressions = compile_formats(@@time_formats)
177
- @@date_expressions = compile_formats(@@date_formats)
178
- @@datetime_expressions = compile_formats(@@datetime_formats)
179
- end
180
-
181
- # Loop through format expressions for type and call proc on matches. Allow
182
- # pre or post match strings to exist if strict is false. Otherwise wrap
183
- # regexp in start and end anchors.
184
- # Returns time array if matches a format, nil otherwise.
185
- def parse(string, type, options={})
186
- return string unless string.is_a?(String)
187
- options.reverse_merge!(:strict => true)
188
-
189
- sets = if options[:format]
190
- options[:strict] = true
191
- [ send("#{type}_expressions").assoc(options[:format]) ]
192
- else
193
- expression_set(type, string)
194
- end
195
-
196
- matches = nil
197
- processor = sets.each do |format, regexp, proc|
198
- full = /\A#{regexp}\Z/ if options[:strict]
199
- full ||= case type
200
- when :date then /\A#{regexp}/
201
- when :time then /#{regexp}\Z/
202
- when :datetime then /\A#{regexp}\Z/
203
- end
204
- break(proc) if matches = full.match(string.strip)
205
- end
206
- last = options[:include_offset] ? 8 : 7
207
- if matches
208
- values = processor.call(*matches[1..last])
209
- values[0..2] = dummy_date_for_time_type if type == :time
210
- return values
211
- end
212
- rescue
213
- nil
214
- end
215
-
216
- # Delete formats of specified type. Error raised if format not found.
217
- def remove_formats(type, *remove_formats)
218
- remove_formats.each do |format|
219
- unless self.send("#{type}_formats").delete(format)
220
- raise "Format #{format} not found in #{type} formats"
221
- end
222
- end
223
- compile_format_expressions
224
- end
225
-
226
- # Adds new formats. Must specify format type and can specify a :before
227
- # option to nominate which format the new formats should be inserted in
228
- # front on to take higher precedence.
229
- # Error is raised if format already exists or if :before format is not found.
230
- def add_formats(type, *add_formats)
231
- formats = self.send("#{type}_formats")
232
- options = {}
233
- options = add_formats.pop if add_formats.last.is_a?(Hash)
234
- before = options[:before]
235
- raise "Format for :before option #{format} was not found." if before && !formats.include?(before)
236
-
237
- add_formats.each do |format|
238
- raise "Format #{format} is already included in #{type} formats" if formats.include?(format)
239
-
240
- index = before ? formats.index(before) : -1
241
- formats.insert(index, format)
242
- end
243
- compile_format_expressions
244
- end
245
-
246
- # Removes formats where the 1 or 2 digit month comes first, to eliminate
247
- # formats which are ambiguous with the European style of day then month.
248
- # The mmm token is ignored as its not ambigous.
249
- def remove_us_formats
250
- us_format_regexp = /\Am{1,2}[^m]/
251
- date_formats.reject! { |format| us_format_regexp =~ format }
252
- datetime_formats.reject! { |format| us_format_regexp =~ format }
253
- compile_format_expressions
254
- end
255
-
256
- def full_hour(hour, meridian)
257
- hour = hour.to_i
258
- return hour if meridian.nil?
259
- if meridian.delete('.').downcase == 'am'
260
- raise if hour == 0 || hour > 12
261
- hour == 12 ? 0 : hour
262
- else
263
- hour == 12 ? hour : hour + 12
264
- end
265
- end
266
-
267
- def unambiguous_year(year)
268
- if year.length <= 2
269
- century = Time.now.year.to_s[0..1].to_i
270
- century -= 1 if year.to_i >= ambiguous_year_threshold
271
- year = "#{century}#{year.rjust(2,'0')}"
272
- end
273
- year.to_i
274
- end
275
-
276
- def month_index(month)
277
- return month.to_i if month.to_i.nonzero?
278
- abbr_month_names.index(month.capitalize) || month_names.index(month.capitalize)
279
- end
280
-
281
- def month_names
282
- defined?(I18n) ? I18n.t('date.month_names') : Date::MONTHNAMES
283
- end
284
-
285
- def abbr_month_names
286
- defined?(I18n) ? I18n.t('date.abbr_month_names') : Date::ABBR_MONTHNAMES
287
- end
288
-
289
- def microseconds(usec)
290
- (".#{usec}".to_f * 1_000_000).to_i
291
- end
292
-
293
- def offset_in_seconds(offset)
294
- sign = offset =~ /^-/ ? -1 : 1
295
- parts = offset.scan(/\d\d/).map {|p| p.to_f }
296
- parts[1] = parts[1].to_f / 60
297
- (parts[0] + parts[1]) * sign * 3600
298
- end
299
-
300
- private
301
-
302
- # Generate regular expression and processor from format string
303
- def generate_format_expression(string_format)
304
- regexp = string_format.dup
305
- order = {}
306
- regexp.gsub!(/([\.\\])/, '\\\\\1') # escapes dots and backslashes
307
-
308
- format_tokens.each do |token|
309
- token_name = token.keys.first
310
- token_regexp, regexp_str, arg_key = *token.values.first
311
-
312
- # hack for lack of look-behinds. If has a capture group then is
313
- # considered an anchor to put straight back in the regexp string.
314
- regexp.gsub!(token_regexp) {|m| "#{$1}" + regexp_str }
315
- order[arg_key] = $~.begin(0) if $~ && !arg_key.nil?
316
- end
317
-
318
- return Regexp.new(regexp), format_proc(order)
319
- rescue
320
- raise "The following format regular expression failed to compile: #{regexp}\n from format #{string_format}."
321
- end
322
-
323
- # Generates a proc which when executed maps the regexp capture groups to a
324
- # proc argument based on order captured. A time array is built using the proc
325
- # argument in the position indicated by the first element of the proc arg
326
- # array.
327
- #
328
- def format_proc(order)
329
- arg_map = format_proc_args
330
- args = order.invert.sort.map {|p| arg_map[p[1]][1] }
331
- arr = [nil] * 7
332
- order.keys.each {|k| i = arg_map[k][0]; arr[i] = arg_map[k][2] unless i.nil? }
333
- proc_string = <<-EOL
334
- lambda {|#{args.join(',')}|
335
- md ||= nil
336
- [#{arr.map {|i| i.nil? ? 'nil' : i }.join(',')}].map {|i| i.is_a?(Float) ? i : i.to_i }
337
- }
338
- EOL
339
- eval proc_string
340
- end
341
-
342
- def compile_formats(formats)
343
- formats.map { |format| [ format, *generate_format_expression(format) ] }
344
- end
345
-
346
- # Pick expression set and combine date and datetimes for
347
- # datetime attributes to allow date string as datetime
348
- def expression_set(type, string)
349
- case type
350
- when :date
351
- date_expressions
352
- when :time
353
- time_expressions
354
- when :datetime
355
- # gives a speed-up for date string as datetime attributes
356
- if string.length < 11
357
- date_expressions + datetime_expressions
358
- else
359
- datetime_expressions + date_expressions
360
- end
361
- end
362
- end
363
-
364
- end
365
- end
366
- end
367
-
368
- ValidatesTimeliness::Formats.compile_format_expressions