hiccup 0.4.0 → 0.4.1

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.
@@ -11,17 +11,13 @@ module Hiccup
11
11
 
12
12
 
13
13
  def enumerator
14
- @enumerator ||= "Hiccup::Enumerable::#{kind.to_s.classify}Enumerator".constantize
15
- end
16
-
17
- def kind=(value)
18
- super
19
- @enumerator = nil
14
+ "Hiccup::Enumerable::#{kind.to_s.classify}Enumerator".constantize
20
15
  end
21
16
 
22
17
 
23
18
 
24
19
  def occurrences_during_month(year, month)
20
+ puts "DEPRECATED: `occurrences_during_month` will be removed in 0.5.0. Use `occurrences_between` instead"
25
21
  date1 = Date.new(year, month, 1)
26
22
  date2 = Date.new(year, month, -1)
27
23
  occurrences_between(date1, date2)
@@ -30,8 +26,6 @@ module Hiccup
30
26
 
31
27
 
32
28
  def occurrences_between(earlier_date, later_date)
33
- earlier_date = start_date if earlier_date < start_date
34
-
35
29
  occurrences = []
36
30
  enum = enumerator.new(self, earlier_date)
37
31
  while (occurrence = enum.next) && (occurrence <= later_date)
@@ -43,42 +37,41 @@ module Hiccup
43
37
 
44
38
 
45
39
  def first_occurrence_on_or_after(date)
46
- date = start_date if (date < start_date)
40
+ return nil if ends? && date > end_date
47
41
  enumerator.new(self, date).next
48
42
  end
49
43
 
50
- def next_occurrence_after(date)
51
- first_occurrence_on_or_after(date + 1)
44
+ def first_occurrence_after(date)
45
+ first_occurrence_on_or_after(date.to_date + 1)
52
46
  end
47
+ alias :next_occurrence_after :first_occurrence_after
53
48
 
54
49
 
55
50
 
56
51
  def first_occurrence_on_or_before(date)
57
- date = end_date if (ends? && date > end_date)
52
+ return nil if date < start_date
58
53
  enumerator.new(self, date).prev
59
54
  end
60
55
 
61
56
  def first_occurrence_before(date)
62
- first_occurrence_on_or_before(date - 1)
57
+ first_occurrence_on_or_before(date.to_date - 1)
63
58
  end
64
59
 
65
60
 
66
61
 
67
62
  def occurs_on(date)
68
63
  date = date.to_date
69
- first_occurrence_on_or_after(date).eql?(date)
64
+ date == first_occurrence_on_or_after(date)
70
65
  end
71
66
  alias :contains? :occurs_on
72
67
 
73
68
 
74
69
 
75
70
  def n_occurrences_before(limit, date)
76
- n_occurrences_on_or_before(limit, date - 1)
71
+ n_occurrences_on_or_before(limit, date.to_date - 1)
77
72
  end
78
73
 
79
74
  def n_occurrences_on_or_before(limit, date)
80
- date = end_date if (ends? && date > end_date)
81
-
82
75
  occurrences = []
83
76
  enum = enumerator.new(self, date)
84
77
  while (occurrence = enum.prev) && occurrences.length < limit
@@ -7,6 +7,8 @@ module Hiccup
7
7
  @schedule = schedule
8
8
  @date = date
9
9
  @date = @date.to_date if @date.respond_to?(:to_date)
10
+ @date = start_date if (@date < start_date)
11
+ @date = end_date if (ends? && @date > end_date)
10
12
  @current_date = nil
11
13
  end
12
14
 
@@ -23,7 +23,7 @@ module Hiccup
23
23
  end
24
24
 
25
25
 
26
- def first_occurrence_on_or_after(date)
26
+ def first_occurrence_on_or_after(date)
27
27
  result = nil
28
28
  wday = date.wday
29
29
  weekly_pattern.each do |weekday|
@@ -10,7 +10,7 @@ module Hiccup
10
10
 
11
11
  def humanize
12
12
  case kind
13
- when :never; ""
13
+ when :never; start_date.to_s
14
14
  when :weekly; weekly_humanize
15
15
  when :monthly; monthly_humanize
16
16
  when :annually; yearly_humanize
@@ -12,48 +12,89 @@ module Hiccup
12
12
  module ClassMethods
13
13
 
14
14
  def infer(dates, options={})
15
- dates = extract_array_of_dates!(dates)
15
+ allow_null_schedules = options.fetch(:allow_null_schedules, false)
16
+ verbosity = options.fetch(:verbosity, (options[:verbose] ? 1 : 0)) # 0, 1, or 2
16
17
 
18
+ dates = extract_array_of_dates!(dates)
17
19
  enumerator = DatesEnumerator.new(dates)
18
- guesser = Guesser.new(self, options)
20
+ guesser = Guesser.new(self, {verbose: verbosity >= 2})
21
+ schedules = []
22
+
19
23
  confidences = []
20
- confidence_threshold = 0.6
21
- rewind_by = 0
24
+ high_confidence_threshold = 0.6
25
+ min_confidence_threshold = 0.35
26
+
27
+ last_confident_schedule = nil
28
+ iterations_since_last_confident_schedule = 0
22
29
 
23
30
  until enumerator.done?
24
31
  date = enumerator.next
25
32
  guesser << date
26
- confidences << guesser.confidence.to_f
33
+ confidence = guesser.confidence.to_f
34
+ confidences << confidence
35
+ predicted = guesser.predicted?(date)
36
+
37
+ # if the last two confidences are both below a certain
38
+ # threshhold and both declining, back up to where we
39
+ # started to go wrong and start a new schedule.
40
+
41
+ confident = !(confidences.length >= 3 && (
42
+ (confidences[-1] < high_confidence_threshold &&
43
+ confidences[-2] < high_confidence_threshold &&
44
+ confidences[-1] < confidences[-2] &&
45
+ confidences[-2] < confidences[-3]) ||
46
+ (confidences[-1] < min_confidence_threshold &&
47
+ confidences[-2] < min_confidence_threshold)))
27
48
 
28
- if guesser.predicted?(date)
29
- rewind_by = 0
49
+ if predicted && confidence >= min_confidence_threshold
50
+ iterations_since_last_confident_schedule = 0
51
+ last_confident_schedule = guesser.schedule
30
52
  else
31
- rewind_by += 1
53
+ iterations_since_last_confident_schedule += 1
32
54
  end
33
55
 
34
- # puts "date: #{date}, confidences: #{confidences}, rewind_by: #{rewind_by}" if options[:verbose]
56
+ rewind_by = iterations_since_last_confident_schedule == guesser.count ? iterations_since_last_confident_schedule - 1 : iterations_since_last_confident_schedule
35
57
 
36
- # if the last two confidences are both below a certain
37
- # threshhold and both declining, back up to where we
38
- # started to go wrong and start a new schedule.
39
58
 
40
- if confidences.length >= 3 &&
41
- confidences[-1] < confidence_threshold &&
42
- confidences[-2] < confidence_threshold &&
43
- confidences[-1] < confidences[-2] &&
44
- confidences[-2] < confidences[-3]
59
+
60
+ if verbosity >= 1
61
+ output = " #{enumerator.index.to_s.rjust(3)} #{date}"
62
+ output << " #{"[#{guesser.count}]".rjust(5)} => "
63
+ output << "~#{(guesser.confidence.to_f * 100).to_i.to_s.rjust(2, "0")} @ "
64
+ output << guesser.schedule.humanize.ljust(130)
65
+ output << " :( move back #{rewind_by}" unless confident
66
+ puts output
67
+ end
68
+
69
+
70
+
71
+ unless confident
72
+
73
+ if last_confident_schedule
74
+ schedules << last_confident_schedule
75
+ elsif allow_null_schedules
76
+ guesser.dates.take(guesser.count - rewind_by).each do |date|
77
+ schedules << self.new(:kind => :never, :start_date => date)
78
+ end
79
+ end
45
80
 
46
- rewind_by -= 1 if rewind_by == guesser.count
47
81
  enumerator.rewind_by(rewind_by)
48
82
  guesser.restart!
49
83
  confidences = []
50
- rewind_by = 0
84
+ iterations_since_last_confident_schedule = 0
85
+ last_confident_schedule = nil
51
86
  end
52
87
  end
53
88
 
54
- guesser.stop!
89
+ if last_confident_schedule
90
+ schedules << last_confident_schedule
91
+ elsif allow_null_schedules
92
+ guesser.dates.each do |date|
93
+ schedules << self.new(:kind => :never, :start_date => date)
94
+ end
95
+ end
55
96
 
56
- guesser.guesses
97
+ schedules
57
98
  end
58
99
 
59
100
 
@@ -83,37 +124,25 @@ module Hiccup
83
124
  def initialize(klass, options={})
84
125
  @klass = klass
85
126
  @verbose = options.fetch(:verbose, false)
86
- @guesses = []
87
127
  start!
88
128
  end
89
129
 
90
- attr_reader :guesses, :confidence
130
+ attr_reader :confidence, :schedule, :dates
91
131
 
92
132
  def start!
93
- save_current_guess!
94
- reset_growing_understanding!
95
- end
96
- alias :restart! :start!
97
-
98
- def stop!
99
- start!
100
- end
101
-
102
- def save_current_guess!
103
- @guesses << @schedule if @schedule
104
- end
105
-
106
- def reset_growing_understanding!
107
133
  @dates = []
108
134
  @schedule = nil
109
135
  @confidence = 0
110
136
  end
137
+ alias :restart! :start!
138
+
111
139
 
112
140
 
113
141
 
114
142
  def <<(date)
115
143
  @dates << date
116
144
  @schedule, @confidence = best_schedule_for(@dates)
145
+ date
117
146
  end
118
147
 
119
148
  def count
@@ -149,7 +178,7 @@ module Hiccup
149
178
  highest_popularity = patterns_by_popularity.keys.max # => 5
150
179
  most_popular = patterns_by_popularity[highest_popularity].first # => a
151
180
  start_date = Date.new(@start_date.year, *most_popular)
152
-
181
+
153
182
  [].tap do |guesses|
154
183
  (1...5).each do |skip|
155
184
  guesses << @klass.new.tap do |schedule|
@@ -267,7 +296,7 @@ module Hiccup
267
296
  puts ""
268
297
  end
269
298
 
270
- scored_guesses.reject { |(guess, score)| score.to_f < 0.333 }.first
299
+ scored_guesses.first
271
300
  end
272
301
 
273
302
  def score_guess(guess, input_dates)
@@ -349,6 +378,8 @@ module Hiccup
349
378
  @index = -1
350
379
  end
351
380
 
381
+ attr_reader :index
382
+
352
383
  def done?
353
384
  @index == @last_index
354
385
  end
@@ -1,3 +1,3 @@
1
1
  module Hiccup
2
- VERSION = "0.4.0"
2
+ VERSION = "0.4.1"
3
3
  end
@@ -189,6 +189,43 @@ class EnumerableTest < ActiveSupport::TestCase
189
189
 
190
190
 
191
191
 
192
+ test "should not predict dates before the beginning of a schedule" do
193
+ schedule = Schedule.new({
194
+ :kind => :weekly,
195
+ :weekly_pattern => %w{Monday},
196
+ :start_date => Date.new(2011, 1, 3),
197
+ :ends => true,
198
+ :end_date => Date.new(2011, 1, 31)})
199
+ assert_equal nil, schedule.first_occurrence_before(Date.new(2011,1,3))
200
+ assert_equal nil, schedule.first_occurrence_on_or_before(Date.new(2011,1,2))
201
+ end
202
+
203
+ test "should not predict dates after the end of a schedule" do
204
+ schedule = Schedule.new({
205
+ :kind => :weekly,
206
+ :weekly_pattern => %w{Monday},
207
+ :start_date => Date.new(2011, 1, 3),
208
+ :ends => true,
209
+ :end_date => Date.new(2011, 1, 31)})
210
+ assert_equal nil, schedule.first_occurrence_after(Date.new(2011,1,31))
211
+ assert_equal nil, schedule.first_occurrence_on_or_after(Date.new(2011,2, 1))
212
+ end
213
+
214
+
215
+
216
+ test "all methods should take any kind of date as an argument" do
217
+ schedule = Schedule.new({
218
+ :kind => :weekly,
219
+ :weekly_pattern => %w{Monday},
220
+ :start_date => Date.new(2011, 1, 1),
221
+ :ends => true,
222
+ :end_date => Date.new(2011, 1, 31)})
223
+ assert_equal Date.new(2011, 1, 17), schedule.first_occurrence_after(Time.new(2011, 1, 10))
224
+ assert_equal Date.new(2011, 1, 3), schedule.first_occurrence_before(Time.new(2011, 1, 10))
225
+ end
226
+
227
+
228
+
192
229
  def test_weekly_recurrence_and_skip
193
230
  schedule = Schedule.new({
194
231
  :kind => :weekly,
@@ -230,13 +230,20 @@ class InferableTest < ActiveSupport::TestCase
230
230
  end
231
231
  end
232
232
 
233
+ test "should create one non-recurring schedule for each break if asked" do
234
+ dates = %w{2012-03-05 2012-11-28 2012-12-05 2012-12-12} # a random Monday, then three Wednesdays
235
+ schedules = Schedule.infer(dates, allow_null_schedules: true)
236
+
237
+ assert_equal ["2012-03-05", "Every Wednesday"], schedules.map(&:humanize)
238
+ end
239
+
233
240
 
234
241
 
235
242
  test "should infer multiple schedules from mixed input" do
236
243
  dates = %w{2012-11-05 2012-11-12 2012-11-19 2012-11-28 2012-12-05 2012-12-12} # three Mondays then three Wednesdays
237
244
  schedules = Schedule.infer(dates)
238
- assert_equal ["Every Monday", "Every Wednesday"],
239
- schedules.map(&:humanize)
245
+
246
+ assert_equal ["Every Monday", "Every Wednesday"], schedules.map(&:humanize)
240
247
  end
241
248
 
242
249
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hiccup
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.4.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-11-12 00:00:00.000000000 Z
12
+ date: 2012-11-21 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activesupport
@@ -179,7 +179,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
179
179
  version: '0'
180
180
  segments:
181
181
  - 0
182
- hash: -1083227062532008741
182
+ hash: -1495672392285233886
183
183
  required_rubygems_version: !ruby/object:Gem::Requirement
184
184
  none: false
185
185
  requirements:
@@ -188,7 +188,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
188
188
  version: '0'
189
189
  segments:
190
190
  - 0
191
- hash: -1083227062532008741
191
+ hash: -1495672392285233886
192
192
  requirements: []
193
193
  rubyforge_project: hiccup
194
194
  rubygems_version: 1.8.24