validates_timeliness 1.1.5 → 1.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG +10 -0
- data/README.rdoc +87 -78
- data/Rakefile +1 -4
- data/TODO +0 -1
- data/lib/validates_timeliness/action_view/instance_tag.rb +5 -2
- data/lib/validates_timeliness/active_record/attribute_methods.rb +26 -36
- data/lib/validates_timeliness/active_record/multiparameter_attributes.rb +12 -10
- data/lib/validates_timeliness/formats.rb +16 -14
- data/lib/validates_timeliness/locale/en.yml +1 -0
- data/lib/validates_timeliness/spec/rails/matchers/validate_timeliness.rb +4 -4
- data/lib/validates_timeliness/validation_methods.rb +2 -2
- data/lib/validates_timeliness/validator.rb +84 -42
- data/lib/validates_timeliness.rb +14 -23
- data/spec/active_record/attribute_methods_spec.rb +6 -4
- data/spec/ginger_scenarios.rb +1 -1
- data/spec/spec_helper.rb +2 -1
- data/spec/validator_spec.rb +124 -14
- metadata +2 -2
data/CHANGELOG
CHANGED
@@ -1,3 +1,13 @@
|
|
1
|
+
= 1.1.6 [2009-03-19]
|
2
|
+
- Rail 2.3 support
|
3
|
+
- Added :with_date and :with_time options. They allow an attribute to be combined with another attribute or value to make a datetime value for validation against the temporal restrictions
|
4
|
+
- Added :equal_to option
|
5
|
+
- Option key validation
|
6
|
+
- Better behaviour with other plugins using alias_method_chain on read_attribute and define_attribute_methods
|
7
|
+
- Added option to enable datetime_select extension for future use to optionally enable. Enabled by default until version 2.
|
8
|
+
- Added :ignore_usec option for datetime restrictions to be compared without microsecond
|
9
|
+
- some refactoring
|
10
|
+
|
1
11
|
= 1.1.5 [2009-01-21]
|
2
12
|
- Fixed regex for 'yy' format token which wasn't greedy enough for date formats ending with year when a datetime string parsed as date with a 4 digit year
|
3
13
|
|
data/README.rdoc
CHANGED
@@ -33,12 +33,16 @@ think should be a valid date or time string.
|
|
33
33
|
|
34
34
|
As plugin (from master)
|
35
35
|
|
36
|
-
./script/plugin git://github.com/adzap/validates_timeliness.git
|
36
|
+
./script/plugin install git://github.com/adzap/validates_timeliness.git
|
37
37
|
|
38
38
|
As gem
|
39
39
|
|
40
40
|
sudo gem install validates_timeliness
|
41
41
|
|
42
|
+
# in environment.rb
|
43
|
+
|
44
|
+
config.gem 'validates_timeliness'
|
45
|
+
|
42
46
|
|
43
47
|
== USAGE:
|
44
48
|
|
@@ -51,50 +55,54 @@ validation method
|
|
51
55
|
end
|
52
56
|
|
53
57
|
The list of validation methods available are as follows:
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
* validates_time - validate value as time only i.e. '12:20pm'
|
58
|
-
|
59
|
-
* validates_datetime - validate value as a full date and time
|
58
|
+
validates_date - validate value as date
|
59
|
+
validates_time - validate value as time only i.e. '12:20pm'
|
60
|
+
validates_datetime - validate value as a full date and time
|
60
61
|
|
61
62
|
The validation methods take the usual options plus some specific ones to restrict
|
62
63
|
the valid range of dates or times allowed
|
63
64
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
65
|
+
Temporal options (or restrictions):
|
66
|
+
:equal_to - Attribute must be equal to value to be valid
|
67
|
+
:before - Attribute must be before this value to be valid
|
68
|
+
:on_or_before - Attribute must be equal to or before this value to be valid
|
69
|
+
:after - Attribute must be after this value to be valid
|
70
|
+
:on_or_after - Attribute must be equal to or after this value to be valid
|
71
|
+
:between - Attribute must be between the values to be valid. Takes an array of two values or a range
|
72
|
+
|
73
|
+
Regular validation options:
|
74
|
+
:allow_nil - Allow a nil value to be valid
|
75
|
+
:allow_blank - Allows a nil or empty string value to be valid
|
76
|
+
:if - Execute validation when :if evaluates true
|
77
|
+
:unless - Execute validation when :unless evaluates false
|
78
|
+
|
79
|
+
Special options:
|
80
|
+
:with_time - Validate a date attribute value combined with a time value against any temporal restrictions
|
81
|
+
:with_date - Validate a time attribute value combined with a date value against any temporal restrictions
|
82
|
+
:ignore_usec - Ignores microsecond value on datetime restrictions
|
83
|
+
|
84
|
+
Message options: - Use these to override the default error messages
|
85
|
+
:invalid_date_message
|
86
|
+
:invalid_time_message
|
87
|
+
:invalid_datetime_message
|
88
|
+
:equal_to_message
|
89
|
+
:before_message
|
90
|
+
:on_or_before_message
|
91
|
+
:after_message
|
92
|
+
:on_or_after_message
|
93
|
+
:between_message
|
94
|
+
|
95
|
+
The temporal restrictions, with_date and with_time can take 4 different value types:
|
96
|
+
* String value
|
97
|
+
* Date, Time, or DateTime object value
|
98
|
+
* Proc or lambda object which may take an optional parameter being the record object
|
99
|
+
* A symbol matching the method name in the model
|
94
100
|
|
95
101
|
When an attribute value is compared to temporal restrictions, they are compared as
|
96
102
|
the same type as the validation method type. So using validates_date means all
|
97
|
-
values are compared as dates.
|
103
|
+
values are compared as dates. This is except in the case of with_time and with_date
|
104
|
+
options which effectively force the value to validated as a datetime against the
|
105
|
+
temporal options.
|
98
106
|
|
99
107
|
== EXAMPLES:
|
100
108
|
|
@@ -107,6 +115,8 @@ values are compared as dates.
|
|
107
115
|
:allow_nil => true
|
108
116
|
|
109
117
|
validates_datetime :appointment_date, :before => Proc.new { 1.week.from_now }
|
118
|
+
|
119
|
+
validates_date :entry_date, :with_time => '17:00', :on_or_before => :competition_closing
|
110
120
|
|
111
121
|
|
112
122
|
=== DATE/TIME FORMATS:
|
@@ -120,44 +130,43 @@ be happy to know that is exactly the format you can use to define your own if
|
|
120
130
|
you want. No complex regular expressions or duck punching (monkey patching) the
|
121
131
|
plugin is needed.
|
122
132
|
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
NOTE: To use non-US date formats see US/EURO FORMATS section
|
133
|
+
==== Time formats:
|
134
|
+
hh:nn:ss
|
135
|
+
hh-nn-ss
|
136
|
+
h:nn
|
137
|
+
h.nn
|
138
|
+
h nn
|
139
|
+
h-nn
|
140
|
+
h:nn_ampm
|
141
|
+
h.nn_ampm
|
142
|
+
h nn_ampm
|
143
|
+
h-nn_ampm
|
144
|
+
h_ampm
|
145
|
+
|
146
|
+
NOTE: Any time format without a meridian token (the 'ampm' token) is considered in 24 hour time.
|
147
|
+
|
148
|
+
==== Date formats:
|
149
|
+
yyyy/mm/dd
|
150
|
+
yyyy-mm-dd
|
151
|
+
yyyy.mm.dd
|
152
|
+
m/d/yy OR d/m/yy
|
153
|
+
m\d\yy OR d\m\yy
|
154
|
+
d-m-yy
|
155
|
+
d.m.yy
|
156
|
+
d mmm yy
|
157
|
+
|
158
|
+
NOTE: To use non-US date formats see US/EURO FORMATS section
|
159
|
+
|
160
|
+
==== Datetime formats:
|
161
|
+
m/d/yy h:nn:ss OR d/m/yy hh:nn:ss
|
162
|
+
m/d/yy h:nn OR d/m/yy h:nn
|
163
|
+
m/d/yy h:nn_ampm OR d/m/yy h:nn_ampm
|
164
|
+
yyyy-mm-dd hh:nn:ss
|
165
|
+
yyyy-mm-dd h:nn
|
166
|
+
ddd mmm d hh:nn:ss zo yyyy # Ruby time string
|
167
|
+
yyyy-mm-ddThh:nn:ss(?:Z|zo) # ISO 8601
|
168
|
+
|
169
|
+
NOTE: To use non-US date formats see US/EURO FORMATS section
|
161
170
|
|
162
171
|
Here is what each format token means:
|
163
172
|
|
@@ -219,7 +228,7 @@ Done! That format is no longer considered valid. Easy!
|
|
219
228
|
Ok, now I hear you say "Well I have format that I want to use but you don't have it".
|
220
229
|
Ahh, then add it yourself. Again stick this in an initializer file
|
221
230
|
|
222
|
-
|
231
|
+
ValidatesTimeliness::Formats.add_formats(:time, "d o'clock")
|
223
232
|
|
224
233
|
Now "10 o'clock" will be a valid value. So easy, no more whingeing!
|
225
234
|
|
@@ -234,7 +243,7 @@ with an existing format, will mean your format is ignored. If you need to make
|
|
234
243
|
your new format higher precedence than an existing format, you can include the
|
235
244
|
before option like so
|
236
245
|
|
237
|
-
|
246
|
+
ValidatesTimeliness::Formats.add_formats(:time, 'ss:nn:hh', :before => 'hh:nn:ss')
|
238
247
|
|
239
248
|
Now a time of '59:30:23' will be interpreted as 11:30:59 pm. This option saves
|
240
249
|
you adding a new one and deleting an old one to get it to work.
|
data/Rakefile
CHANGED
@@ -5,7 +5,7 @@ require 'date'
|
|
5
5
|
require 'spec/rake/spectask'
|
6
6
|
|
7
7
|
GEM = "validates_timeliness"
|
8
|
-
GEM_VERSION = "1.1.
|
8
|
+
GEM_VERSION = "1.1.6"
|
9
9
|
AUTHOR = "Adam Meehan"
|
10
10
|
EMAIL = "adam.meehan@gmail.com"
|
11
11
|
HOMEPAGE = "http://github.com/adzap/validates_timeliness"
|
@@ -24,9 +24,6 @@ spec = Gem::Specification.new do |s|
|
|
24
24
|
s.email = EMAIL
|
25
25
|
s.homepage = HOMEPAGE
|
26
26
|
|
27
|
-
# Uncomment this to add a dependency
|
28
|
-
# s.add_dependency "foo"
|
29
|
-
|
30
27
|
s.require_path = 'lib'
|
31
28
|
s.autorequire = GEM
|
32
29
|
s.files = %w(LICENSE README.rdoc Rakefile TODO CHANGELOG) + Dir.glob("{lib,spec}/**/*")
|
data/TODO
CHANGED
@@ -1,4 +1,9 @@
|
|
1
1
|
module ValidatesTimeliness
|
2
|
+
|
3
|
+
def self.enable_datetime_select_invalid_value_extension!
|
4
|
+
::ActionView::Helpers::InstanceTag.send(:include, ValidatesTimeliness::ActionView::InstanceTag)
|
5
|
+
end
|
6
|
+
|
2
7
|
module ActionView
|
3
8
|
|
4
9
|
# Intercepts the date and time select helpers to allow the
|
@@ -41,5 +46,3 @@ module ValidatesTimeliness
|
|
41
46
|
|
42
47
|
end
|
43
48
|
end
|
44
|
-
|
45
|
-
ActionView::Helpers::InstanceTag.send(:include, ValidatesTimeliness::ActionView::InstanceTag)
|
@@ -18,29 +18,27 @@ module ValidatesTimeliness
|
|
18
18
|
|
19
19
|
def self.included(base)
|
20
20
|
base.extend ClassMethods
|
21
|
+
base.class_eval do
|
22
|
+
alias_method_chain :read_attribute, :timeliness
|
23
|
+
class << self
|
24
|
+
alias_method_chain :define_attribute_methods, :timeliness
|
25
|
+
end
|
26
|
+
end
|
21
27
|
end
|
22
28
|
|
23
29
|
# Adds check for cached date/time attributes which have been type cast already
|
24
30
|
# and value can be used from cache. This prevents the raw date/time value from
|
25
31
|
# being type cast using default Rails type casting when writing values
|
26
32
|
# to the database.
|
27
|
-
def
|
33
|
+
def read_attribute_with_timeliness(attr_name)
|
28
34
|
attr_name = attr_name.to_s
|
29
35
|
if !(value = @attributes[attr_name]).nil?
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
elsif [:date, :time, :datetime].include?(column.type) && @attributes_cache.has_key?(attr_name)
|
34
|
-
@attributes_cache[attr_name]
|
35
|
-
else
|
36
|
-
column.type_cast(value)
|
37
|
-
end
|
38
|
-
else
|
39
|
-
value
|
36
|
+
column = column_for_attribute(attr_name)
|
37
|
+
if column && [:date, :time, :datetime].include?(column.type) && @attributes_cache.has_key?(attr_name)
|
38
|
+
return @attributes_cache[attr_name]
|
40
39
|
end
|
41
|
-
else
|
42
|
-
nil
|
43
40
|
end
|
41
|
+
read_attribute_without_timeliness(attr_name)
|
44
42
|
end
|
45
43
|
|
46
44
|
# If Rails dirty attributes is enabled then the value is added to
|
@@ -48,19 +46,20 @@ module ValidatesTimeliness
|
|
48
46
|
# implementation as it chains the write_attribute method which deletes
|
49
47
|
# the attribute from the cache.
|
50
48
|
def write_date_time_attribute(attr_name, value, type, time_zone_aware)
|
51
|
-
old = read_attribute(attr_name) if defined?(::ActiveRecord::Dirty)
|
52
49
|
new = self.class.parse_date_time(value, type)
|
53
50
|
|
54
|
-
|
55
|
-
new = new.to_time
|
51
|
+
if new && type != :date
|
52
|
+
new = new.to_time
|
53
|
+
new = new.in_time_zone if time_zone_aware
|
56
54
|
end
|
57
55
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
56
|
+
if defined?(::ActiveRecord::Dirty) && !changed_attributes.include?(attr_name)
|
57
|
+
old = read_attribute(attr_name)
|
58
|
+
if old != new
|
59
|
+
changed_attributes[attr_name] = (old.duplicable? ? old.clone : old)
|
60
|
+
end
|
63
61
|
end
|
62
|
+
@attributes_cache[attr_name] = new
|
64
63
|
@attributes[attr_name] = value
|
65
64
|
end
|
66
65
|
|
@@ -74,7 +73,7 @@ module ValidatesTimeliness
|
|
74
73
|
|
75
74
|
if @attributes_cache.has_key?(attr_name)
|
76
75
|
time = read_attribute_before_type_cast(attr_name)
|
77
|
-
time = self.class.parse_date_time(
|
76
|
+
time = self.class.parse_date_time(time, type)
|
78
77
|
else
|
79
78
|
time = read_attribute(attr_name)
|
80
79
|
@attributes[attr_name] = time && time_zone_aware ? time.in_time_zone : time
|
@@ -84,19 +83,15 @@ module ValidatesTimeliness
|
|
84
83
|
|
85
84
|
module ClassMethods
|
86
85
|
|
87
|
-
#
|
88
|
-
#
|
89
|
-
def
|
86
|
+
# Define attribute reader and writer method for date, time and
|
87
|
+
# datetime attributes to use plugin parser.
|
88
|
+
def define_attribute_methods_with_timeliness
|
90
89
|
return if generated_methods?
|
91
90
|
columns_hash.each do |name, column|
|
92
91
|
unless instance_method_already_implemented?(name)
|
93
|
-
if
|
94
|
-
define_read_method_for_serialized_attribute(name)
|
95
|
-
elsif [:date, :time, :datetime].include?(column.type)
|
92
|
+
if [:date, :time, :datetime].include?(column.type)
|
96
93
|
time_zone_aware = create_time_zone_conversion_attribute?(name, column) rescue false
|
97
94
|
define_read_method_for_dates_and_times(name, column.type, time_zone_aware)
|
98
|
-
else
|
99
|
-
define_read_method(name.to_sym, name, column)
|
100
95
|
end
|
101
96
|
end
|
102
97
|
|
@@ -104,15 +99,10 @@ module ValidatesTimeliness
|
|
104
99
|
if [:date, :time, :datetime].include?(column.type)
|
105
100
|
time_zone_aware = create_time_zone_conversion_attribute?(name, column) rescue false
|
106
101
|
define_write_method_for_dates_and_times(name, column.type, time_zone_aware)
|
107
|
-
else
|
108
|
-
define_write_method(name.to_sym)
|
109
102
|
end
|
110
103
|
end
|
111
|
-
|
112
|
-
unless instance_method_already_implemented?("#{name}?")
|
113
|
-
define_question_method(name)
|
114
|
-
end
|
115
104
|
end
|
105
|
+
define_attribute_methods_without_timeliness
|
116
106
|
end
|
117
107
|
|
118
108
|
# Define write method for date, time and datetime columns
|
@@ -1,6 +1,10 @@
|
|
1
1
|
module ValidatesTimeliness
|
2
|
-
module ActiveRecord
|
3
2
|
|
3
|
+
def self.enable_multiparameter_attributes_extension!
|
4
|
+
::ActiveRecord::Base.send(:include, ValidatesTimeliness::ActiveRecord::MultiparameterAttributes)
|
5
|
+
end
|
6
|
+
|
7
|
+
module ActiveRecord
|
4
8
|
module MultiparameterAttributes
|
5
9
|
|
6
10
|
def self.included(base)
|
@@ -38,13 +42,13 @@ module ValidatesTimeliness
|
|
38
42
|
values = values.map(&:to_s)
|
39
43
|
|
40
44
|
case type
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
45
|
+
when :date
|
46
|
+
extract_date_from_multiparameter_attributes(values)
|
47
|
+
when :time
|
48
|
+
extract_time_from_multiparameter_attributes(values)
|
49
|
+
when :datetime
|
50
|
+
date_values, time_values = values.slice!(0, 3), values
|
51
|
+
extract_date_from_multiparameter_attributes(date_values) + " " + extract_time_from_multiparameter_attributes(time_values)
|
48
52
|
end
|
49
53
|
end
|
50
54
|
|
@@ -60,5 +64,3 @@ module ValidatesTimeliness
|
|
60
64
|
|
61
65
|
end
|
62
66
|
end
|
63
|
-
|
64
|
-
ActiveRecord::Base.send(:include, ValidatesTimeliness::ActiveRecord::MultiparameterAttributes)
|
@@ -168,9 +168,9 @@ module ValidatesTimeliness
|
|
168
168
|
exp, processor = expression_set(type, string).find do |regexp, proc|
|
169
169
|
full = /\A#{regexp}\Z/ if strict
|
170
170
|
full ||= case type
|
171
|
-
|
172
|
-
|
173
|
-
|
171
|
+
when :date then /\A#{regexp}/
|
172
|
+
when :time then /#{regexp}\Z/
|
173
|
+
when :datetime then /\A#{regexp}\Z/
|
174
174
|
end
|
175
175
|
matches = full.match(string.strip)
|
176
176
|
end
|
@@ -268,17 +268,17 @@ module ValidatesTimeliness
|
|
268
268
|
# datetime attributes to allow date string as datetime
|
269
269
|
def expression_set(type, string)
|
270
270
|
case type
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
271
|
+
when :date
|
272
|
+
date_expressions
|
273
|
+
when :time
|
274
|
+
time_expressions
|
275
|
+
when :datetime
|
276
|
+
# gives a speed-up for date string as datetime attributes
|
277
|
+
if string.length < 11
|
278
|
+
date_expressions + datetime_expressions
|
279
|
+
else
|
280
|
+
datetime_expressions + date_expressions
|
281
|
+
end
|
282
282
|
end
|
283
283
|
end
|
284
284
|
|
@@ -316,3 +316,5 @@ module ValidatesTimeliness
|
|
316
316
|
end
|
317
317
|
end
|
318
318
|
end
|
319
|
+
|
320
|
+
ValidatesTimeliness::Formats.compile_format_expressions
|
@@ -5,6 +5,7 @@ en:
|
|
5
5
|
invalid_date: "is not a valid date"
|
6
6
|
invalid_time: "is not a valid time"
|
7
7
|
invalid_datetime: "is not a valid datetime"
|
8
|
+
equal_to: "must be equal to {{restriction}}"
|
8
9
|
before: "must be before {{restriction}}"
|
9
10
|
on_or_before: "must be on or before {{restriction}}"
|
10
11
|
after: "must be after {{restriction}}"
|
@@ -92,8 +92,8 @@ module Spec
|
|
92
92
|
end
|
93
93
|
|
94
94
|
def parse_and_cast(value)
|
95
|
-
value = @validator.send(:
|
96
|
-
@validator.send(:type_cast_value, value)
|
95
|
+
value = @validator.class.send(:evaluate_option_value, value, @type, @record)
|
96
|
+
@validator.class.send(:type_cast_value, value, @type)
|
97
97
|
end
|
98
98
|
|
99
99
|
def error_matching(value, option)
|
@@ -117,11 +117,11 @@ module Spec
|
|
117
117
|
|
118
118
|
def error_message_for(option)
|
119
119
|
msg = @validator.send(:error_messages)[option]
|
120
|
-
restriction = @validator.send(:
|
120
|
+
restriction = @validator.class.send(:evaluate_option_value, @validator.configuration[option], @type, @record)
|
121
121
|
|
122
122
|
if restriction
|
123
123
|
restriction = [restriction] unless restriction.is_a?(Array)
|
124
|
-
restriction.map! {|r| @validator.send(:type_cast_value, r) }
|
124
|
+
restriction.map! {|r| @validator.class.send(:type_cast_value, r, @type) }
|
125
125
|
interpolate = @validator.send(:interpolation_values, option, restriction )
|
126
126
|
# get I18n message if defined and has interpolation keys in msg
|
127
127
|
if defined?(I18n) && !@validator.send(:custom_error_messages).include?(option)
|
@@ -49,13 +49,13 @@ module ValidatesTimeliness
|
|
49
49
|
private
|
50
50
|
|
51
51
|
def validates_timeliness_of(attr_names, configuration)
|
52
|
-
validator = ValidatesTimeliness::Validator.new(configuration)
|
52
|
+
validator = ValidatesTimeliness::Validator.new(configuration.symbolize_keys)
|
53
53
|
|
54
54
|
# bypass handling of allow_nil and allow_blank to validate raw value
|
55
55
|
configuration.delete(:allow_nil)
|
56
56
|
configuration.delete(:allow_blank)
|
57
57
|
validates_each(attr_names, configuration) do |record, attr_name, value|
|
58
|
-
validator.call(record, attr_name)
|
58
|
+
validator.call(record, attr_name, value)
|
59
59
|
end
|
60
60
|
end
|
61
61
|
|
@@ -12,6 +12,7 @@ module ValidatesTimeliness
|
|
12
12
|
}
|
13
13
|
|
14
14
|
RESTRICTION_METHODS = {
|
15
|
+
:equal_to => :==,
|
15
16
|
:before => :<,
|
16
17
|
:after => :>,
|
17
18
|
:on_or_before => :<=,
|
@@ -19,18 +20,24 @@ module ValidatesTimeliness
|
|
19
20
|
:between => lambda {|v, r| (r.first..r.last).include?(v) }
|
20
21
|
}
|
21
22
|
|
23
|
+
VALID_OPTIONS = [
|
24
|
+
:on, :if, :unless, :allow_nil, :empty, :allow_blank, :blank,
|
25
|
+
:with_time, :with_date, :ignore_usec,
|
26
|
+
:invalid_time_message, :invalid_date_message, :invalid_datetime_message
|
27
|
+
] + RESTRICTION_METHODS.keys.map {|option| [option, "#{option}_message".to_sym] }.flatten
|
28
|
+
|
22
29
|
attr_reader :configuration, :type
|
23
30
|
|
24
31
|
def initialize(configuration)
|
25
|
-
defaults = { :on => :save, :type => :datetime, :allow_nil => false, :allow_blank => false }
|
32
|
+
defaults = { :on => :save, :type => :datetime, :allow_nil => false, :allow_blank => false, :ignore_usec => false }
|
26
33
|
@configuration = defaults.merge(configuration)
|
27
34
|
@type = @configuration.delete(:type)
|
35
|
+
validate_options(@configuration)
|
28
36
|
end
|
29
37
|
|
30
|
-
def call(record, attr_name)
|
31
|
-
value = record.send(attr_name)
|
38
|
+
def call(record, attr_name, value)
|
32
39
|
value = record.class.parse_date_time(value, type, false) if value.is_a?(String)
|
33
|
-
raw_value = raw_value(record, attr_name)
|
40
|
+
raw_value = raw_value(record, attr_name) || value
|
34
41
|
|
35
42
|
return if (raw_value.nil? && configuration[:allow_nil]) || (raw_value.blank? && configuration[:allow_blank])
|
36
43
|
|
@@ -44,18 +51,25 @@ module ValidatesTimeliness
|
|
44
51
|
private
|
45
52
|
|
46
53
|
def raw_value(record, attr_name)
|
47
|
-
record.send("#{attr_name}_before_type_cast")
|
54
|
+
record.send("#{attr_name}_before_type_cast") rescue nil
|
48
55
|
end
|
49
56
|
|
50
57
|
def validate_restrictions(record, attr_name, value)
|
51
|
-
value =
|
52
|
-
|
58
|
+
value = if @configuration[:with_time] || @configuration[:with_date]
|
59
|
+
restriction_type = :datetime
|
60
|
+
combine_date_and_time(value, record)
|
61
|
+
else
|
62
|
+
restriction_type = type
|
63
|
+
self.class.type_cast_value(value, type, @configuration[:ignore_usec])
|
64
|
+
end
|
65
|
+
return if value.nil?
|
66
|
+
|
53
67
|
RESTRICTION_METHODS.each do |option, method|
|
54
68
|
next unless restriction = configuration[option]
|
55
69
|
begin
|
56
|
-
restriction =
|
70
|
+
restriction = self.class.evaluate_option_value(restriction, restriction_type, record)
|
57
71
|
next if restriction.nil?
|
58
|
-
restriction = type_cast_value(restriction)
|
72
|
+
restriction = self.class.type_cast_value(restriction, restriction_type, @configuration[:ignore_usec])
|
59
73
|
|
60
74
|
unless evaluate_restriction(restriction, value, method)
|
61
75
|
add_error(record, attr_name, option, interpolation_values(option, restriction))
|
@@ -87,10 +101,10 @@ module ValidatesTimeliness
|
|
87
101
|
return true if restriction.nil?
|
88
102
|
|
89
103
|
case comparator
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
104
|
+
when Symbol
|
105
|
+
value.send(comparator, restriction)
|
106
|
+
when Proc
|
107
|
+
comparator.call(value, restriction)
|
94
108
|
end
|
95
109
|
end
|
96
110
|
|
@@ -107,13 +121,11 @@ module ValidatesTimeliness
|
|
107
121
|
end
|
108
122
|
|
109
123
|
def error_messages
|
110
|
-
|
111
|
-
@error_messages = ValidatesTimeliness.default_error_messages.merge(custom_error_messages)
|
124
|
+
@error_messages ||= ValidatesTimeliness.default_error_messages.merge(custom_error_messages)
|
112
125
|
end
|
113
126
|
|
114
127
|
def custom_error_messages
|
115
|
-
|
116
|
-
@custom_error_messages = configuration.inject({}) {|msgs, (k, v)|
|
128
|
+
@custom_error_messages ||= configuration.inject({}) {|msgs, (k, v)|
|
117
129
|
if md = /(.*)_message$/.match(k.to_s)
|
118
130
|
msgs[md[1].to_sym] = v
|
119
131
|
end
|
@@ -121,42 +133,72 @@ module ValidatesTimeliness
|
|
121
133
|
}
|
122
134
|
end
|
123
135
|
|
124
|
-
def
|
125
|
-
|
136
|
+
def combine_date_and_time(value, record)
|
137
|
+
if type == :date
|
138
|
+
date = value
|
139
|
+
time = @configuration[:with_time]
|
140
|
+
else
|
141
|
+
date = @configuration[:with_date]
|
142
|
+
time = value
|
143
|
+
end
|
144
|
+
date, time = self.class.evaluate_option_value(date, :date, record), self.class.evaluate_option_value(time, :time, record)
|
145
|
+
return if date.nil? || time.nil?
|
146
|
+
record.class.send(:make_time, [date.year, date.month, date.day, time.hour, time.min, time.sec, time.usec])
|
147
|
+
end
|
148
|
+
|
149
|
+
def validate_options(options)
|
150
|
+
invalid_for_type = ([:time, :date, :datetime] - [@type]).map {|k| "invalid_#{k}_message".to_sym }
|
151
|
+
invalid_for_type << :with_date unless @type == :time
|
152
|
+
invalid_for_type << :with_time unless @type == :date
|
153
|
+
options.assert_valid_keys(VALID_OPTIONS - invalid_for_type)
|
154
|
+
end
|
155
|
+
|
156
|
+
# class methods
|
157
|
+
class << self
|
158
|
+
|
159
|
+
def evaluate_option_value(value, type, record)
|
160
|
+
case value
|
126
161
|
when Time, Date, DateTime
|
127
|
-
|
162
|
+
value
|
128
163
|
when Symbol
|
129
|
-
|
164
|
+
evaluate_option_value(record.send(value), type, record)
|
130
165
|
when Proc
|
131
|
-
|
166
|
+
evaluate_option_value(value.call(record), type, record)
|
132
167
|
when Array
|
133
|
-
|
168
|
+
value.map {|r| evaluate_option_value(r, type, record) }.sort
|
134
169
|
when Range
|
135
|
-
|
170
|
+
evaluate_option_value([value.first, value.last], type, record)
|
136
171
|
else
|
137
|
-
|
172
|
+
record.class.parse_date_time(value, type, false)
|
173
|
+
end
|
138
174
|
end
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
175
|
+
|
176
|
+
def type_cast_value(value, type, ignore_usec=false)
|
177
|
+
if value.is_a?(Array)
|
178
|
+
value.map {|v| type_cast_value(v, type, ignore_usec) }
|
179
|
+
else
|
180
|
+
value = case type
|
181
|
+
when :time
|
182
|
+
value.to_dummy_time
|
183
|
+
when :date
|
184
|
+
value.to_date
|
185
|
+
when :datetime
|
186
|
+
if value.is_a?(DateTime) || value.is_a?(Time)
|
187
|
+
value.to_time
|
188
|
+
else
|
189
|
+
value.to_time(ValidatesTimeliness.default_timezone)
|
190
|
+
end
|
153
191
|
else
|
154
|
-
|
192
|
+
nil
|
193
|
+
end
|
194
|
+
if ignore_usec && value.is_a?(Time)
|
195
|
+
::ActiveRecord::Base.send(:make_time, Array(value).reverse[4..9])
|
196
|
+
else
|
197
|
+
value
|
155
198
|
end
|
156
|
-
else
|
157
|
-
nil
|
158
199
|
end
|
159
200
|
end
|
201
|
+
|
160
202
|
end
|
161
203
|
|
162
204
|
end
|
data/lib/validates_timeliness.rb
CHANGED
@@ -21,14 +21,20 @@ module ValidatesTimeliness
|
|
21
21
|
|
22
22
|
class << self
|
23
23
|
|
24
|
-
def
|
25
|
-
|
24
|
+
def enable_datetime_select_extension!
|
25
|
+
enable_datetime_select_invalid_value_extension!
|
26
|
+
enable_multiparameter_attributes_extension!
|
26
27
|
end
|
27
28
|
|
28
|
-
def
|
29
|
-
|
30
|
-
|
31
|
-
|
29
|
+
def load_error_messages
|
30
|
+
if defined?(I18n)
|
31
|
+
I18n.load_path += [ LOCALE_PATH ]
|
32
|
+
I18n.reload!
|
33
|
+
else
|
34
|
+
messages = YAML::load(IO.read(LOCALE_PATH))
|
35
|
+
errors = messages['en']['activerecord']['errors']['messages'].inject({}) {|h,(k,v)| h[k.to_sym] = v.gsub(/\{\{\w*\}\}/, '%s');h }
|
36
|
+
::ActiveRecord::Errors.default_error_messages.update(errors)
|
37
|
+
end
|
32
38
|
end
|
33
39
|
|
34
40
|
def default_error_messages
|
@@ -39,28 +45,13 @@ module ValidatesTimeliness
|
|
39
45
|
end
|
40
46
|
end
|
41
47
|
|
42
|
-
def setup_for_rails_2_0
|
43
|
-
load_error_messages_without_i18n
|
44
|
-
end
|
45
|
-
|
46
|
-
def setup_for_rails_2_1
|
47
|
-
load_error_messages_without_i18n
|
48
|
-
end
|
49
|
-
|
50
|
-
def setup_for_rails_2_2
|
51
|
-
load_error_messages_with_i18n
|
52
|
-
end
|
53
|
-
|
54
48
|
def setup_for_rails
|
55
49
|
major, minor = Rails::VERSION::MAJOR, Rails::VERSION::MINOR
|
56
50
|
self.default_timezone = ::ActiveRecord::Base.default_timezone
|
57
|
-
self.
|
58
|
-
|
59
|
-
puts "Rails version #{major}.#{minor}.x not explicitly supported by validates_timeliness plugin. You may encounter some problems."
|
51
|
+
self.enable_datetime_select_extension!
|
52
|
+
self.load_error_messages
|
60
53
|
end
|
61
54
|
end
|
62
55
|
end
|
63
56
|
|
64
57
|
ValidatesTimeliness.setup_for_rails
|
65
|
-
|
66
|
-
ValidatesTimeliness::Formats.compile_format_expressions
|
@@ -1,9 +1,6 @@
|
|
1
1
|
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
2
|
|
3
3
|
describe ValidatesTimeliness::ActiveRecord::AttributeMethods do
|
4
|
-
include ValidatesTimeliness::ActiveRecord::AttributeMethods
|
5
|
-
include ValidatesTimeliness::ValidationMethods
|
6
|
-
|
7
4
|
before do
|
8
5
|
@person = Person.new
|
9
6
|
end
|
@@ -35,7 +32,7 @@ describe ValidatesTimeliness::ActiveRecord::AttributeMethods do
|
|
35
32
|
@person.birth_time
|
36
33
|
end
|
37
34
|
|
38
|
-
it "should call
|
35
|
+
it "should call read_date_time_attribute when datetime attribute is retrieved" do
|
39
36
|
@person.should_receive(:read_date_time_attribute)
|
40
37
|
@person.birth_date_and_time = "2000-01-01 12:00"
|
41
38
|
@person.birth_date_and_time
|
@@ -72,6 +69,11 @@ describe ValidatesTimeliness::ActiveRecord::AttributeMethods do
|
|
72
69
|
@person.birth_date_and_time.should be_kind_of(Time)
|
73
70
|
end
|
74
71
|
|
72
|
+
it "should return Time object for datetime attribute read method when assigned Date object" do
|
73
|
+
@person.birth_date_and_time = Date.today
|
74
|
+
@person.birth_date_and_time.should be_kind_of(Time)
|
75
|
+
end
|
76
|
+
|
75
77
|
it "should return Time object for datetime attribute read method when assigned string" do
|
76
78
|
@person.birth_date_and_time = "2000-01-01 02:03:04"
|
77
79
|
@person.birth_date_and_time.should be_kind_of(Time)
|
data/spec/ginger_scenarios.rb
CHANGED
data/spec/spec_helper.rb
CHANGED
@@ -2,7 +2,7 @@ $:.unshift(File.dirname(__FILE__) + '/../lib')
|
|
2
2
|
$:.unshift(File.dirname(__FILE__))
|
3
3
|
$:.unshift(File.dirname(__FILE__) + '/resources')
|
4
4
|
|
5
|
-
ENV['RAILS_ENV'] = 'test'
|
5
|
+
RAILS_ENV = ENV['RAILS_ENV'] = 'test'
|
6
6
|
|
7
7
|
require 'rubygems'
|
8
8
|
require 'spec'
|
@@ -30,6 +30,7 @@ require 'active_record'
|
|
30
30
|
require 'active_record/version'
|
31
31
|
require 'action_controller'
|
32
32
|
require 'action_view'
|
33
|
+
require 'action_mailer'
|
33
34
|
|
34
35
|
require 'spec/rails'
|
35
36
|
require 'time_travel/time_travel'
|
data/spec/validator_spec.rb
CHANGED
@@ -16,55 +16,71 @@ describe ValidatesTimeliness::Validator do
|
|
16
16
|
@person = Person.new
|
17
17
|
end
|
18
18
|
|
19
|
-
describe "
|
19
|
+
describe "option keys validation" do
|
20
|
+
before do
|
21
|
+
keys = ValidatesTimeliness::Validator::VALID_OPTIONS - [:invalid_date_message, :invalid_time_message, :with_date, :with_time]
|
22
|
+
@valid_options = keys.inject({}) {|hash, opt| hash[opt] = nil; hash }
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should raise error if invalid option key passed" do
|
26
|
+
@valid_options.update(:invalid_key => 'will not open lock')
|
27
|
+
lambda { Person.validates_datetime(@valid_options) }.should raise_error(ArgumentError)
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should not raise error if option keys are valid" do
|
31
|
+
lambda { Person.validates_datetime(@valid_options) }.should_not raise_error(ArgumentError)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe "evaluate_option_value" do
|
20
36
|
it "should return Time object when restriction is Time object" do
|
21
|
-
|
37
|
+
evaluate_option_value(Time.now, :datetime).should be_kind_of(Time)
|
22
38
|
end
|
23
39
|
|
24
40
|
it "should return Time object when restriction is string" do
|
25
|
-
|
41
|
+
evaluate_option_value("2007-01-01 12:00", :datetime).should be_kind_of(Time)
|
26
42
|
end
|
27
43
|
|
28
44
|
it "should return Time object when restriction is method and method returns Time object" do
|
29
45
|
person.stub!(:datetime_attr).and_return(Time.now)
|
30
|
-
|
46
|
+
evaluate_option_value(:datetime_attr, :datetime).should be_kind_of(Time)
|
31
47
|
end
|
32
48
|
|
33
49
|
it "should return Time object when restriction is method and method returns string" do
|
34
50
|
person.stub!(:datetime_attr).and_return("2007-01-01 12:00")
|
35
|
-
|
51
|
+
evaluate_option_value(:datetime_attr, :datetime).should be_kind_of(Time)
|
36
52
|
end
|
37
53
|
|
38
54
|
it "should return Time object when restriction is proc which returns Time object" do
|
39
|
-
|
55
|
+
evaluate_option_value(lambda { Time.now }, :datetime).should be_kind_of(Time)
|
40
56
|
end
|
41
57
|
|
42
58
|
it "should return Time object when restriction is proc which returns string" do
|
43
|
-
|
59
|
+
evaluate_option_value(lambda {"2007-01-01 12:00"}, :datetime).should be_kind_of(Time)
|
44
60
|
end
|
45
61
|
|
46
62
|
it "should return array of Time objects when restriction is array of Time objects" do
|
47
63
|
time1, time2 = Time.now, 1.day.ago
|
48
|
-
|
64
|
+
evaluate_option_value([time1, time2], :datetime).should == [time2, time1]
|
49
65
|
end
|
50
66
|
|
51
67
|
it "should return array of Time objects when restriction is array of strings" do
|
52
68
|
time1, time2 = "2000-01-02", "2000-01-01"
|
53
|
-
|
69
|
+
evaluate_option_value([time1, time2], :datetime).should == [Person.parse_date_time(time2, :datetime), Person.parse_date_time(time1, :datetime)]
|
54
70
|
end
|
55
71
|
|
56
72
|
it "should return array of Time objects when restriction is Range of Time objects" do
|
57
73
|
time1, time2 = Time.now, 1.day.ago
|
58
|
-
|
74
|
+
evaluate_option_value(time1..time2, :datetime).should == [time2, time1]
|
59
75
|
end
|
60
76
|
|
61
77
|
it "should return array of Time objects when restriction is Range of time strings" do
|
62
78
|
time1, time2 = "2000-01-02", "2000-01-01"
|
63
|
-
|
79
|
+
evaluate_option_value(time1..time2, :datetime).should == [Person.parse_date_time(time2, :datetime), Person.parse_date_time(time1, :datetime)]
|
64
80
|
end
|
65
|
-
def
|
81
|
+
def evaluate_option_value(restriction, type)
|
66
82
|
configure_validator(:type => type)
|
67
|
-
validator.send(:
|
83
|
+
validator.class.send(:evaluate_option_value, restriction, type, person)
|
68
84
|
end
|
69
85
|
end
|
70
86
|
|
@@ -330,6 +346,100 @@ describe ValidatesTimeliness::Validator do
|
|
330
346
|
end
|
331
347
|
end
|
332
348
|
|
349
|
+
describe "instance with :equal_to restriction" do
|
350
|
+
|
351
|
+
describe "for datetime type" do
|
352
|
+
before do
|
353
|
+
configure_validator(:equal_to => Time.now)
|
354
|
+
end
|
355
|
+
|
356
|
+
it "should have error when value not equal to :equal_to restriction" do
|
357
|
+
validate_with(:birth_date_and_time, Time.now + 1)
|
358
|
+
should_have_error(:birth_date_and_time, :equal_to)
|
359
|
+
end
|
360
|
+
|
361
|
+
it "should have error when value is equal to :equal_to restriction for all values except microscond, and microsecond is not ignored" do
|
362
|
+
configure_validator(:equal_to => Time.utc(2000, 1, 1, 0, 0, 0, 0), :ignore_usec => false)
|
363
|
+
validate_with(:birth_date_and_time, Time.utc(2000, 1, 1, 0, 0, 0, 500))
|
364
|
+
should_have_error(:birth_date_and_time, :equal_to)
|
365
|
+
end
|
366
|
+
|
367
|
+
it "should be valid when value is equal to :equal_to restriction for all values except microscond, and microsecond is ignored" do
|
368
|
+
configure_validator(:equal_to => Time.utc(2000, 1, 1, 0, 0, 0, 0), :ignore_usec => true)
|
369
|
+
validate_with(:birth_date_and_time, Time.utc(2000, 1, 1, 0, 0, 0, 500))
|
370
|
+
should_have_no_error(:birth_date_and_time, :equal_to)
|
371
|
+
end
|
372
|
+
|
373
|
+
it "should be valid when value is equal to :equal_to restriction" do
|
374
|
+
validate_with(:birth_date_and_time, Time.now)
|
375
|
+
should_have_no_error(:birth_date_and_time, :equal_to)
|
376
|
+
end
|
377
|
+
end
|
378
|
+
|
379
|
+
describe "for date type" do
|
380
|
+
before do
|
381
|
+
configure_validator(:type => :date, :equal_to => Date.today)
|
382
|
+
end
|
383
|
+
|
384
|
+
it "should have error when value is not equal to :equal_to restriction" do
|
385
|
+
validate_with(:birth_date, Date.today + 1)
|
386
|
+
should_have_error(:birth_date, :equal_to)
|
387
|
+
end
|
388
|
+
|
389
|
+
it "should be valid when value is equal to :equal_to restriction" do
|
390
|
+
validate_with(:birth_date, Date.today)
|
391
|
+
should_have_no_error(:birth_date, :equal_to)
|
392
|
+
end
|
393
|
+
end
|
394
|
+
|
395
|
+
describe "for time type" do
|
396
|
+
before do
|
397
|
+
configure_validator(:type => :time, :equal_to => "09:00:00")
|
398
|
+
end
|
399
|
+
|
400
|
+
it "should have error when value is not equal to :equal_to restriction" do
|
401
|
+
validate_with(:birth_time, "09:00:01")
|
402
|
+
should_have_error(:birth_time, :equal_to)
|
403
|
+
end
|
404
|
+
|
405
|
+
it "should be valid when value is equal to :equal_to restriction" do
|
406
|
+
validate_with(:birth_time, "09:00:00")
|
407
|
+
should_have_no_error(:birth_time, :equal_to)
|
408
|
+
end
|
409
|
+
end
|
410
|
+
end
|
411
|
+
|
412
|
+
describe "instance with :with_time option" do
|
413
|
+
|
414
|
+
it "should validate date attribute as datetime combining value of :with_time against restrictions " do
|
415
|
+
configure_validator(:type => :date, :with_time => '12:31', :on_or_before => Time.mktime(2000,1,1,12,30))
|
416
|
+
validate_with(:birth_date, "2000-01-01")
|
417
|
+
should_have_error(:birth_date, :on_or_before)
|
418
|
+
end
|
419
|
+
|
420
|
+
it "should skip restriction validation if :with_time value is nil" do
|
421
|
+
configure_validator(:type => :date, :with_time => nil, :on_or_before => Time.mktime(2000,1,1,12,30))
|
422
|
+
validate_with(:birth_date, "2000-01-01")
|
423
|
+
should_have_no_error(:birth_date, :on_or_before)
|
424
|
+
end
|
425
|
+
|
426
|
+
end
|
427
|
+
|
428
|
+
describe "instance with :with_date option" do
|
429
|
+
|
430
|
+
it "should validate time attribute as datetime combining value of :with_date against restrictions " do
|
431
|
+
configure_validator(:type => :time, :with_date => '2009-01-01', :on_or_before => Time.mktime(2000,1,1,12,30))
|
432
|
+
validate_with(:birth_date, "12:30")
|
433
|
+
should_have_error(:birth_date, :on_or_before)
|
434
|
+
end
|
435
|
+
|
436
|
+
it "should skip restriction validation if :with_date value is nil" do
|
437
|
+
configure_validator(:type => :time, :with_date => nil, :on_or_before => Time.mktime(2000,1,1,12,30))
|
438
|
+
validate_with(:birth_date, "12:30")
|
439
|
+
should_have_no_error(:birth_date, :on_or_before)
|
440
|
+
end
|
441
|
+
end
|
442
|
+
|
333
443
|
describe "instance with mixed value and restriction types" do
|
334
444
|
|
335
445
|
it "should validate datetime attribute with Date restriction" do
|
@@ -483,7 +593,7 @@ describe ValidatesTimeliness::Validator do
|
|
483
593
|
|
484
594
|
def validate_with(attr_name, value)
|
485
595
|
person.send("#{attr_name}=", value)
|
486
|
-
validator.call(person, attr_name)
|
596
|
+
validator.call(person, attr_name, value)
|
487
597
|
end
|
488
598
|
|
489
599
|
def should_have_error(attr_name, error)
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: validates_timeliness
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.1.
|
4
|
+
version: 1.1.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Adam Meehan
|
@@ -9,7 +9,7 @@ autorequire: validates_timeliness
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-
|
12
|
+
date: 2009-03-19 00:00:00 +11:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|