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.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/lib/hiccup.rb +17 -17
  3. data/lib/hiccup/convenience.rb +9 -9
  4. data/lib/hiccup/core_ext/date.rb +7 -7
  5. data/lib/hiccup/core_ext/duration.rb +4 -4
  6. data/lib/hiccup/core_ext/enumerable.rb +2 -2
  7. data/lib/hiccup/core_ext/fixnum.rb +2 -2
  8. data/lib/hiccup/core_ext/hash.rb +2 -2
  9. data/lib/hiccup/enumerable.rb +43 -29
  10. data/lib/hiccup/enumerable/annually_enumerator.rb +23 -23
  11. data/lib/hiccup/enumerable/monthly_date_enumerator.rb +2 -2
  12. data/lib/hiccup/enumerable/monthly_enumerator.rb +40 -40
  13. data/lib/hiccup/enumerable/never_enumerator.rb +8 -8
  14. data/lib/hiccup/enumerable/schedule_enumerator.rb +39 -39
  15. data/lib/hiccup/enumerable/weekly_enumerator.rb +30 -30
  16. data/lib/hiccup/errors.rb +4 -0
  17. data/lib/hiccup/humanizable.rb +19 -19
  18. data/lib/hiccup/inferable.rb +30 -30
  19. data/lib/hiccup/inferable/dates_enumerator.rb +6 -6
  20. data/lib/hiccup/inferable/guesser.rb +21 -21
  21. data/lib/hiccup/inferable/score.rb +7 -7
  22. data/lib/hiccup/inferable/scorer.rb +19 -19
  23. data/lib/hiccup/schedule.rb +10 -10
  24. data/lib/hiccup/serializable/ical.rb +13 -13
  25. data/lib/hiccup/serializers/ical.rb +59 -59
  26. data/lib/hiccup/validatable.rb +23 -23
  27. data/lib/hiccup/version.rb +1 -1
  28. data/test/core_ext_date_test.rb +5 -5
  29. data/test/duration_ext_test.rb +8 -8
  30. data/test/enumerable_test.rb +103 -103
  31. data/test/humanizable_test.rb +24 -24
  32. data/test/ical_serializable_test.rb +29 -29
  33. data/test/inferrable_test.rb +84 -84
  34. data/test/leap_year_test.rb +7 -7
  35. data/test/monthly_enumerator_test.rb +13 -13
  36. data/test/performance_test.rb +7 -7
  37. data/test/validatable_test.rb +1 -1
  38. data/test/weekly_enumerator_test.rb +38 -38
  39. 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
@@ -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
@@ -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