hiccup 0.5.14 → 0.5.15
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.
- checksums.yaml +4 -4
- data/lib/hiccup.rb +17 -17
- data/lib/hiccup/convenience.rb +9 -9
- data/lib/hiccup/core_ext/date.rb +7 -7
- data/lib/hiccup/core_ext/duration.rb +4 -4
- data/lib/hiccup/core_ext/enumerable.rb +2 -2
- data/lib/hiccup/core_ext/fixnum.rb +2 -2
- data/lib/hiccup/core_ext/hash.rb +2 -2
- data/lib/hiccup/enumerable.rb +43 -29
- data/lib/hiccup/enumerable/annually_enumerator.rb +23 -23
- data/lib/hiccup/enumerable/monthly_date_enumerator.rb +2 -2
- data/lib/hiccup/enumerable/monthly_enumerator.rb +40 -40
- data/lib/hiccup/enumerable/never_enumerator.rb +8 -8
- data/lib/hiccup/enumerable/schedule_enumerator.rb +39 -39
- data/lib/hiccup/enumerable/weekly_enumerator.rb +30 -30
- data/lib/hiccup/errors.rb +4 -0
- data/lib/hiccup/humanizable.rb +19 -19
- data/lib/hiccup/inferable.rb +30 -30
- data/lib/hiccup/inferable/dates_enumerator.rb +6 -6
- data/lib/hiccup/inferable/guesser.rb +21 -21
- data/lib/hiccup/inferable/score.rb +7 -7
- data/lib/hiccup/inferable/scorer.rb +19 -19
- data/lib/hiccup/schedule.rb +10 -10
- data/lib/hiccup/serializable/ical.rb +13 -13
- data/lib/hiccup/serializers/ical.rb +59 -59
- data/lib/hiccup/validatable.rb +23 -23
- data/lib/hiccup/version.rb +1 -1
- data/test/core_ext_date_test.rb +5 -5
- data/test/duration_ext_test.rb +8 -8
- data/test/enumerable_test.rb +103 -103
- data/test/humanizable_test.rb +24 -24
- data/test/ical_serializable_test.rb +29 -29
- data/test/inferrable_test.rb +84 -84
- data/test/leap_year_test.rb +7 -7
- data/test/monthly_enumerator_test.rb +13 -13
- data/test/performance_test.rb +7 -7
- data/test/validatable_test.rb +1 -1
- data/test/weekly_enumerator_test.rb +38 -38
- metadata +4 -3
@@ -1,18 +1,18 @@
|
|
1
1
|
module Hiccup
|
2
2
|
module Inferable
|
3
3
|
class Scorer
|
4
|
-
|
4
|
+
|
5
5
|
def initialize(options={})
|
6
6
|
@verbose = options.fetch(:verbose, false)
|
7
7
|
end
|
8
|
-
|
9
|
-
|
10
|
-
|
8
|
+
|
9
|
+
|
10
|
+
|
11
11
|
def pick_best_guess(guesses, dates)
|
12
12
|
scored_guesses = guesses \
|
13
13
|
.map { |guess| [guess, score_guess(guess, dates)] } \
|
14
14
|
.sort_by { |(guess, score)| -score.to_f }
|
15
|
-
|
15
|
+
|
16
16
|
if @verbose
|
17
17
|
puts "\nGUESSES FOR #{dates}:"
|
18
18
|
scored_guesses.each do |(guess, score)|
|
@@ -24,44 +24,44 @@ module Hiccup
|
|
24
24
|
end
|
25
25
|
puts ""
|
26
26
|
end
|
27
|
-
|
27
|
+
|
28
28
|
scored_guesses.first
|
29
29
|
end
|
30
|
-
|
31
|
-
|
32
|
-
|
30
|
+
|
31
|
+
|
32
|
+
|
33
33
|
def score_guess(guess, input_dates)
|
34
34
|
predicted_dates = guess.occurrences_between(guess.start_date, guess.end_date)
|
35
|
-
|
35
|
+
|
36
36
|
# prediction_rate is the percent of input dates predicted
|
37
37
|
predictions = (predicted_dates & input_dates).length
|
38
38
|
prediction_rate = Float(predictions) / Float(input_dates.length)
|
39
|
-
|
39
|
+
|
40
40
|
# bricks are dates predicted by this guess but not in the input
|
41
41
|
bricks = (predicted_dates - input_dates).length
|
42
|
-
|
42
|
+
|
43
43
|
# brick_rate is the percent of bricks to predictions
|
44
44
|
# A brick_rate >= 1 means that this guess bricks more than it predicts
|
45
45
|
brick_rate = Float(bricks) / Float(input_dates.length)
|
46
|
-
|
46
|
+
|
47
47
|
# complexity measures how many rules are necesary
|
48
48
|
# to describe the pattern
|
49
49
|
complexity = complexity_of(guess)
|
50
|
-
|
50
|
+
|
51
51
|
# complexity_rate is the number of rules per inputs
|
52
52
|
complexity_rate = Float(complexity) / Float(input_dates.length)
|
53
|
-
|
53
|
+
|
54
54
|
Score.new(prediction_rate, brick_rate, complexity_rate)
|
55
55
|
end
|
56
|
-
|
57
|
-
|
58
|
-
|
56
|
+
|
57
|
+
|
58
|
+
|
59
59
|
def complexity_of(schedule)
|
60
60
|
return schedule.weekly_pattern.length if schedule.weekly?
|
61
61
|
return schedule.monthly_pattern.length if schedule.monthly?
|
62
62
|
1
|
63
63
|
end
|
64
|
-
|
64
|
+
|
65
65
|
end
|
66
66
|
end
|
67
67
|
end
|
data/lib/hiccup/schedule.rb
CHANGED
@@ -6,15 +6,15 @@ module Hiccup
|
|
6
6
|
class Schedule
|
7
7
|
extend Hiccup
|
8
8
|
include ActiveModel::Validations
|
9
|
-
|
10
|
-
|
9
|
+
|
10
|
+
|
11
11
|
hiccup :enumerable,
|
12
12
|
:validatable,
|
13
13
|
:humanizable,
|
14
14
|
:inferable,
|
15
15
|
:serializable => [:ical]
|
16
|
-
|
17
|
-
|
16
|
+
|
17
|
+
|
18
18
|
def initialize(options={})
|
19
19
|
@kind =(options[:kind] || :never).to_sym
|
20
20
|
@start_date =(options[:start_date] || Date.today).to_date
|
@@ -24,11 +24,11 @@ module Hiccup
|
|
24
24
|
@weekly_pattern = options[:weekly_pattern] || []
|
25
25
|
@monthly_pattern = options[:monthly_pattern] || []
|
26
26
|
end
|
27
|
-
|
28
|
-
|
27
|
+
|
28
|
+
|
29
29
|
attr_accessor :kind, :start_date, :ends, :end_date, :skip, :weekly_pattern, :monthly_pattern
|
30
|
-
|
31
|
-
|
30
|
+
|
31
|
+
|
32
32
|
def to_hash
|
33
33
|
{
|
34
34
|
:kind => kind,
|
@@ -39,7 +39,7 @@ module Hiccup
|
|
39
39
|
:monthly_pattern => monthly_pattern
|
40
40
|
}
|
41
41
|
end
|
42
|
-
|
43
|
-
|
42
|
+
|
43
|
+
|
44
44
|
end
|
45
45
|
end
|
@@ -6,34 +6,34 @@ module Hiccup
|
|
6
6
|
module Serializable
|
7
7
|
module Ical
|
8
8
|
extend ActiveSupport::Concern
|
9
|
-
|
10
|
-
|
9
|
+
|
10
|
+
|
11
11
|
def to_ical
|
12
12
|
ical_serializer.dump(self)
|
13
13
|
end
|
14
|
-
|
15
|
-
|
14
|
+
|
15
|
+
|
16
16
|
module ClassMethods
|
17
|
-
|
17
|
+
|
18
18
|
def from_ical(ics)
|
19
19
|
ical_serializer.load(ics)
|
20
20
|
end
|
21
|
-
|
21
|
+
|
22
22
|
def ical_serializer
|
23
23
|
@ical_serializer ||= Serializers::Ical.new(self)
|
24
24
|
end
|
25
|
-
|
25
|
+
|
26
26
|
end
|
27
|
-
|
28
|
-
|
27
|
+
|
28
|
+
|
29
29
|
private
|
30
|
-
|
31
|
-
|
30
|
+
|
31
|
+
|
32
32
|
def ical_serializer
|
33
33
|
self.class.ical_serializer
|
34
34
|
end
|
35
|
-
|
36
|
-
|
35
|
+
|
36
|
+
|
37
37
|
end
|
38
38
|
end
|
39
39
|
end
|
@@ -4,38 +4,38 @@ require "ri_cal"
|
|
4
4
|
module Hiccup
|
5
5
|
module Serializers
|
6
6
|
class Ical
|
7
|
-
|
7
|
+
|
8
8
|
def initialize(klass)
|
9
9
|
@klass = klass
|
10
10
|
end
|
11
|
-
|
12
|
-
|
13
|
-
|
11
|
+
|
12
|
+
|
13
|
+
|
14
14
|
def dump(obj)
|
15
15
|
@component = RiCal::Component::Event.new
|
16
16
|
@component.dtstart = obj.start_date.to_time if obj.start_date
|
17
17
|
@obj = obj
|
18
|
-
|
18
|
+
|
19
19
|
case obj.kind
|
20
20
|
when :weekly; add_weekly_rule
|
21
21
|
when :monthly; add_monthly_rule
|
22
22
|
when :annually; add_yearly_rule
|
23
23
|
end
|
24
|
-
|
24
|
+
|
25
25
|
StringIO.new.tap {|io|
|
26
26
|
@component.export_properties_to(io)
|
27
27
|
@component.export_x_properties_to(io)
|
28
28
|
}.string
|
29
29
|
end
|
30
|
-
|
31
|
-
|
32
|
-
|
30
|
+
|
31
|
+
|
32
|
+
|
33
33
|
def load(ics)
|
34
34
|
return unless ics # Required for now, for ActiveRecord
|
35
|
-
|
35
|
+
|
36
36
|
component_ics = "BEGIN:VEVENT\n#{ics}\nEND:VEVENT"
|
37
37
|
component = RiCal.parse_string(component_ics).first
|
38
|
-
|
38
|
+
|
39
39
|
@obj = @klass.new
|
40
40
|
@obj.start_date = component.dtstart_property.try(:to_datetime)
|
41
41
|
component.rrule_property.each do |rule|
|
@@ -45,22 +45,22 @@ module Hiccup
|
|
45
45
|
when "YEARLY"; parse_yearly_rule(rule)
|
46
46
|
end
|
47
47
|
end
|
48
|
-
|
48
|
+
|
49
49
|
@obj
|
50
50
|
end
|
51
|
-
|
52
|
-
|
53
|
-
|
51
|
+
|
52
|
+
|
53
|
+
|
54
54
|
private
|
55
|
-
|
56
|
-
|
57
|
-
|
55
|
+
|
56
|
+
|
57
|
+
|
58
58
|
def add_weekly_rule
|
59
59
|
add_rule("WEEKLY", :byday => abbreviate_weekdays(@obj.weekly_pattern))
|
60
60
|
end
|
61
|
-
|
62
|
-
|
63
|
-
|
61
|
+
|
62
|
+
|
63
|
+
|
64
64
|
def add_monthly_rule
|
65
65
|
byday = []
|
66
66
|
bymonthday = []
|
@@ -72,19 +72,19 @@ module Hiccup
|
|
72
72
|
bymonthday << occurrence
|
73
73
|
end
|
74
74
|
end
|
75
|
-
|
75
|
+
|
76
76
|
add_rule("MONTHLY", :bymonthday => bymonthday) if bymonthday.any?
|
77
77
|
add_rule("MONTHLY", :byday => byday) if byday.any?
|
78
78
|
end
|
79
|
-
|
80
|
-
|
81
|
-
|
79
|
+
|
80
|
+
|
81
|
+
|
82
82
|
def add_yearly_rule
|
83
83
|
add_rule("YEARLY")
|
84
84
|
end
|
85
|
-
|
86
|
-
|
87
|
-
|
85
|
+
|
86
|
+
|
87
|
+
|
88
88
|
def add_rule(freq, options={})
|
89
89
|
merge_default_options_for_new_rule!(freq, options)
|
90
90
|
parent = options.delete(:parent)
|
@@ -92,7 +92,7 @@ module Hiccup
|
|
92
92
|
rrule = RiCal::PropertyValue::RecurrenceRule.new(parent, options)
|
93
93
|
@component.rrule_property.push(rrule)
|
94
94
|
end
|
95
|
-
|
95
|
+
|
96
96
|
def merge_default_options_for_new_rule!(freq, options)
|
97
97
|
options.merge!({
|
98
98
|
:freq => freq,
|
@@ -100,47 +100,47 @@ module Hiccup
|
|
100
100
|
:until => @obj.ends? && @obj.end_date && @obj.end_date.to_time
|
101
101
|
})
|
102
102
|
end
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
103
|
+
|
104
|
+
|
105
|
+
|
106
|
+
|
107
|
+
|
108
108
|
def parse_weekly_rule(rule)
|
109
109
|
@obj.kind = :weekly
|
110
110
|
@obj.weekly_pattern = backmap_weekdays(rule.by_list[:byday])
|
111
111
|
parse_rule(rule)
|
112
112
|
end
|
113
|
-
|
114
|
-
|
115
|
-
|
113
|
+
|
114
|
+
|
115
|
+
|
116
116
|
def parse_monthly_rule(rule)
|
117
117
|
@obj.kind = :monthly
|
118
118
|
parse_monthly_bymonthyday(rule.by_list[:bymonthday])
|
119
119
|
parse_monthly_byday(rule.by_list[:byday])
|
120
120
|
parse_rule(rule)
|
121
121
|
end
|
122
|
-
|
122
|
+
|
123
123
|
def parse_monthly_bymonthyday(bymonthday)
|
124
124
|
(bymonthday || []).each do |bymonthday|
|
125
125
|
@obj.monthly_pattern = @obj.monthly_pattern + [bymonthday.ordinal]
|
126
126
|
end
|
127
127
|
end
|
128
|
-
|
128
|
+
|
129
129
|
def parse_monthly_byday(byday)
|
130
130
|
(byday || []).each do |byday|
|
131
131
|
@obj.monthly_pattern = @obj.monthly_pattern + [[byday.index, backmap_weekday(byday)]]
|
132
132
|
end
|
133
133
|
end
|
134
|
-
|
135
|
-
|
136
|
-
|
134
|
+
|
135
|
+
|
136
|
+
|
137
137
|
def parse_yearly_rule(rule)
|
138
138
|
@obj.kind = :annually
|
139
139
|
parse_rule(rule)
|
140
140
|
end
|
141
|
-
|
142
|
-
|
143
|
-
|
141
|
+
|
142
|
+
|
143
|
+
|
144
144
|
def parse_rule(rule)
|
145
145
|
@obj.skip = rule.interval
|
146
146
|
if rule.until
|
@@ -148,30 +148,30 @@ module Hiccup
|
|
148
148
|
@obj.end_date = rule.until.to_datetime
|
149
149
|
end
|
150
150
|
end
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
151
|
+
|
152
|
+
|
153
|
+
|
154
|
+
|
155
|
+
|
156
156
|
def abbreviate_weekdays(weekdays)
|
157
157
|
weekdays.map(&method(:abbreviate_weekday)).compact
|
158
158
|
end
|
159
|
-
|
159
|
+
|
160
160
|
def abbreviate_weekday(weekday)
|
161
161
|
WEEKDAY_MAP[weekday.to_s.downcase]
|
162
162
|
end
|
163
|
-
|
163
|
+
|
164
164
|
def backmap_weekdays(byday)
|
165
165
|
byday ||= []
|
166
166
|
byday.map(&method(:backmap_weekday)).compact
|
167
167
|
end
|
168
|
-
|
168
|
+
|
169
169
|
def backmap_weekday(byday)
|
170
170
|
Date::DAYNAMES[byday.wday]
|
171
171
|
end
|
172
|
-
|
173
|
-
|
174
|
-
|
172
|
+
|
173
|
+
|
174
|
+
|
175
175
|
WEEKDAY_MAP = {
|
176
176
|
"sunday" => "SU",
|
177
177
|
"monday" => "MO",
|
@@ -181,9 +181,9 @@ module Hiccup
|
|
181
181
|
"friday" => "FR",
|
182
182
|
"saturday" => "SA"
|
183
183
|
}
|
184
|
-
|
185
|
-
|
186
|
-
|
184
|
+
|
185
|
+
|
186
|
+
|
187
187
|
end
|
188
188
|
end
|
189
189
|
end
|
data/lib/hiccup/validatable.rb
CHANGED
@@ -6,18 +6,18 @@ module Hiccup
|
|
6
6
|
module Validatable
|
7
7
|
include Convenience
|
8
8
|
extend ActiveSupport::Concern
|
9
|
-
|
10
|
-
|
9
|
+
|
10
|
+
|
11
11
|
# !todo: use ActiveModel:Validation rather than a custom method
|
12
12
|
included do
|
13
13
|
validates :skip, numericality: {greater_than: 0}
|
14
14
|
validate :validate_recurrence
|
15
15
|
end
|
16
|
-
|
17
|
-
|
16
|
+
|
17
|
+
|
18
18
|
private
|
19
|
-
|
20
|
-
|
19
|
+
|
20
|
+
|
21
21
|
# !todo: use i18n to let clients of this library supply their own wording
|
22
22
|
def validate_recurrence
|
23
23
|
case kind
|
@@ -27,7 +27,7 @@ module Hiccup
|
|
27
27
|
when :annually;
|
28
28
|
else; invalid_kind!
|
29
29
|
end
|
30
|
-
|
30
|
+
|
31
31
|
errors.add :start_date, "is a #{start_date.class} not a Date" unless start_date.is_a?(Date)
|
32
32
|
if ends?
|
33
33
|
if end_date.is_a?(Date)
|
@@ -37,8 +37,8 @@ module Hiccup
|
|
37
37
|
end
|
38
38
|
end
|
39
39
|
end
|
40
|
-
|
41
|
-
|
40
|
+
|
41
|
+
|
42
42
|
def validate_weekly_recurrence
|
43
43
|
if !weekly_pattern.is_a?(Array)
|
44
44
|
errors.add(:weekly_pattern, "is a #{weekly_pattern.class}. It should be an array")
|
@@ -48,8 +48,8 @@ module Hiccup
|
|
48
48
|
errors.add(:weekly_pattern, "should contain only weekdays. (#{invalid_names.to_sentence} are invalid)")
|
49
49
|
end
|
50
50
|
end
|
51
|
-
|
52
|
-
|
51
|
+
|
52
|
+
|
53
53
|
def validate_monthly_recurrence
|
54
54
|
if !monthly_pattern.is_a?(Array)
|
55
55
|
errors.add(:monthly_pattern, "is a #{monthly_pattern.class}. It should be an array")
|
@@ -59,12 +59,12 @@ module Hiccup
|
|
59
59
|
errors.add(:monthly_pattern, "contains invalid monthly occurrences")
|
60
60
|
end
|
61
61
|
end
|
62
|
-
|
63
|
-
|
62
|
+
|
63
|
+
|
64
64
|
def invalid_occurrence?(occurrence)
|
65
65
|
!valid_occurrence?(occurrence)
|
66
66
|
end
|
67
|
-
|
67
|
+
|
68
68
|
def valid_occurrence?(occurrence)
|
69
69
|
if occurrence.is_a?(Array)
|
70
70
|
i, wd = occurrence
|
@@ -74,26 +74,26 @@ module Hiccup
|
|
74
74
|
i.is_a?(Fixnum) && (1..31).include?(i)
|
75
75
|
end
|
76
76
|
end
|
77
|
-
|
78
|
-
|
77
|
+
|
78
|
+
|
79
79
|
def invalid_kind!
|
80
80
|
errors.add(:kind, "#{kind.inspect} is not recognized. It must be one of #{Kinds.collect{|kind| ":#{kind}"}.to_sentence(:two_words_connector => " or ", :last_word_connector => ", or ")}.")
|
81
81
|
end
|
82
|
-
|
83
|
-
|
82
|
+
|
83
|
+
|
84
84
|
# def valid_occurrence?(occurrence)
|
85
85
|
# if occurrence.is_a?(Array)
|
86
86
|
# ordinal, kind = occurrence
|
87
|
-
#
|
87
|
+
#
|
88
88
|
# errors.add(:kind, "is not a valid monthly occurrence kind") unless Date::DAYNAMES.member?(kind)
|
89
89
|
# if ordinal.is_a?(Fixnum)
|
90
|
-
# errors.add(:ordinal, "is not a valid integer") unless (ordinal==-1) or (1..6).include?(ordinal)
|
90
|
+
# errors.add(:ordinal, "is not a valid integer") unless (ordinal==-1) or (1..6).include?(ordinal)
|
91
91
|
# else
|
92
92
|
# errors.add(:ordinal, "is not an integer")
|
93
93
|
# end
|
94
94
|
# else
|
95
95
|
# ordinal = occurrence
|
96
|
-
#
|
96
|
+
#
|
97
97
|
# if ordinal.is_a?(Fixnum)
|
98
98
|
# errors.add(:ordinal, "is not an integer between 1 and 31") unless (1..31).include?(ordinal)
|
99
99
|
# else
|
@@ -101,7 +101,7 @@ module Hiccup
|
|
101
101
|
# end
|
102
102
|
# end
|
103
103
|
# end
|
104
|
-
|
105
|
-
|
104
|
+
|
105
|
+
|
106
106
|
end
|
107
107
|
end
|