ice_cube_conrad 0.8.0

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 (42) hide show
  1. data/lib/ice_cube.rb +80 -0
  2. data/lib/ice_cube/builders/hash_builder.rb +27 -0
  3. data/lib/ice_cube/builders/ical_builder.rb +59 -0
  4. data/lib/ice_cube/builders/string_builder.rb +74 -0
  5. data/lib/ice_cube/deprecated.rb +28 -0
  6. data/lib/ice_cube/errors/count_exceeded.rb +7 -0
  7. data/lib/ice_cube/errors/until_exceeded.rb +7 -0
  8. data/lib/ice_cube/errors/zero_interval.rb +7 -0
  9. data/lib/ice_cube/rule.rb +182 -0
  10. data/lib/ice_cube/rules/daily_rule.rb +14 -0
  11. data/lib/ice_cube/rules/hourly_rule.rb +14 -0
  12. data/lib/ice_cube/rules/minutely_rule.rb +14 -0
  13. data/lib/ice_cube/rules/monthly_rule.rb +14 -0
  14. data/lib/ice_cube/rules/secondly_rule.rb +13 -0
  15. data/lib/ice_cube/rules/weekly_rule.rb +14 -0
  16. data/lib/ice_cube/rules/yearly_rule.rb +14 -0
  17. data/lib/ice_cube/schedule.rb +414 -0
  18. data/lib/ice_cube/single_occurrence_rule.rb +28 -0
  19. data/lib/ice_cube/time_util.rb +250 -0
  20. data/lib/ice_cube/validated_rule.rb +108 -0
  21. data/lib/ice_cube/validations/count.rb +56 -0
  22. data/lib/ice_cube/validations/daily_interval.rb +55 -0
  23. data/lib/ice_cube/validations/day.rb +65 -0
  24. data/lib/ice_cube/validations/day_of_month.rb +52 -0
  25. data/lib/ice_cube/validations/day_of_week.rb +70 -0
  26. data/lib/ice_cube/validations/day_of_year.rb +55 -0
  27. data/lib/ice_cube/validations/hour_of_day.rb +52 -0
  28. data/lib/ice_cube/validations/hourly_interval.rb +57 -0
  29. data/lib/ice_cube/validations/lock.rb +47 -0
  30. data/lib/ice_cube/validations/minute_of_hour.rb +51 -0
  31. data/lib/ice_cube/validations/minutely_interval.rb +57 -0
  32. data/lib/ice_cube/validations/month_of_year.rb +49 -0
  33. data/lib/ice_cube/validations/monthly_interval.rb +51 -0
  34. data/lib/ice_cube/validations/schedule_lock.rb +41 -0
  35. data/lib/ice_cube/validations/second_of_minute.rb +49 -0
  36. data/lib/ice_cube/validations/secondly_interval.rb +54 -0
  37. data/lib/ice_cube/validations/until.rb +51 -0
  38. data/lib/ice_cube/validations/weekly_interval.rb +60 -0
  39. data/lib/ice_cube/validations/yearly_interval.rb +49 -0
  40. data/lib/ice_cube/version.rb +5 -0
  41. data/spec/spec_helper.rb +11 -0
  42. metadata +120 -0
data/lib/ice_cube.rb ADDED
@@ -0,0 +1,80 @@
1
+ require 'date'
2
+ require 'ice_cube/deprecated'
3
+
4
+ # Use psych if we can
5
+ begin
6
+ require 'psych'
7
+ rescue LoadError
8
+ require 'yaml'
9
+ end
10
+
11
+ module IceCube
12
+
13
+ autoload :VERSION, 'ice_cube/version'
14
+
15
+ autoload :TimeUtil, 'ice_cube/time_util'
16
+
17
+ autoload :Rule, 'ice_cube/rule'
18
+ autoload :Schedule, 'ice_cube/schedule'
19
+
20
+ autoload :IcalBuilder, 'ice_cube/builders/ical_builder'
21
+ autoload :HashBuilder, 'ice_cube/builders/hash_builder'
22
+ autoload :StringBuilder, 'ice_cube/builders/string_builder'
23
+
24
+ autoload :CountExceeded, 'ice_cube/errors/count_exceeded'
25
+ autoload :UntilExceeded, 'ice_cube/errors/until_exceeded'
26
+ autoload :ZeroInterval, 'ice_cube/errors/zero_interval'
27
+
28
+ autoload :ValidatedRule, 'ice_cube/validated_rule'
29
+ autoload :SingleOccurrenceRule, 'ice_cube/single_occurrence_rule'
30
+
31
+ autoload :SecondlyRule, 'ice_cube/rules/secondly_rule'
32
+ autoload :MinutelyRule, 'ice_cube/rules/minutely_rule'
33
+ autoload :HourlyRule, 'ice_cube/rules/hourly_rule'
34
+ autoload :DailyRule, 'ice_cube/rules/daily_rule'
35
+ autoload :WeeklyRule, 'ice_cube/rules/weekly_rule'
36
+ autoload :MonthlyRule, 'ice_cube/rules/monthly_rule'
37
+ autoload :YearlyRule, 'ice_cube/rules/yearly_rule'
38
+
39
+ module Validations
40
+
41
+ autoload :Lock, 'ice_cube/validations/lock'
42
+ autoload :ScheduleLock, 'ice_cube/validations/schedule_lock'
43
+
44
+ autoload :Count, 'ice_cube/validations/count'
45
+ autoload :Until, 'ice_cube/validations/until'
46
+
47
+ autoload :SecondlyInterval, 'ice_cube/validations/secondly_interval'
48
+ autoload :MinutelyInterval, 'ice_cube/validations/minutely_interval'
49
+ autoload :DailyInterval, 'ice_cube/validations/daily_interval'
50
+ autoload :WeeklyInterval, 'ice_cube/validations/weekly_interval'
51
+ autoload :MonthlyInterval, 'ice_cube/validations/monthly_interval'
52
+ autoload :YearlyInterval, 'ice_cube/validations/yearly_interval'
53
+ autoload :HourlyInterval, 'ice_cube/validations/hourly_interval'
54
+
55
+ autoload :HourOfDay, 'ice_cube/validations/hour_of_day'
56
+ autoload :MonthOfYear, 'ice_cube/validations/month_of_year'
57
+ autoload :MinuteOfHour, 'ice_cube/validations/minute_of_hour'
58
+ autoload :SecondOfMinute, 'ice_cube/validations/second_of_minute'
59
+ autoload :DayOfMonth, 'ice_cube/validations/day_of_month'
60
+ autoload :DayOfWeek, 'ice_cube/validations/day_of_week'
61
+ autoload :Day, 'ice_cube/validations/day'
62
+ autoload :DayOfYear, 'ice_cube/validations/day_of_year'
63
+
64
+ end
65
+
66
+ # Define some useful constants
67
+ ONE_SECOND = 1
68
+ ONE_MINUTE = ONE_SECOND * 60
69
+ ONE_HOUR = ONE_MINUTE * 60
70
+ ONE_DAY = ONE_HOUR * 24
71
+ ONE_WEEK = ONE_DAY * 7
72
+
73
+ # Formatting
74
+ TO_S_TIME_FORMAT = '%B %e, %Y'
75
+
76
+ def self.use_psych?
77
+ @use_psych ||= defined?(Psych) && defined?(Psych::VERSION)
78
+ end
79
+
80
+ end
@@ -0,0 +1,27 @@
1
+ module IceCube
2
+
3
+ class HashBuilder
4
+
5
+ def initialize(rule = nil)
6
+ @hash = { :validations => {}, :rule_type => rule.class.name }
7
+ end
8
+
9
+ def validations
10
+ @hash[:validations]
11
+ end
12
+
13
+ def []=(key, value)
14
+ @hash[key] = value
15
+ end
16
+
17
+ def validations_array(type)
18
+ validations[type] ||= []
19
+ end
20
+
21
+ def to_hash
22
+ @hash
23
+ end
24
+
25
+ end
26
+
27
+ end
@@ -0,0 +1,59 @@
1
+ module IceCube
2
+
3
+ class IcalBuilder
4
+
5
+ ICAL_DAYS = ['SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA']
6
+
7
+ def initialize
8
+ @hash = {}
9
+ end
10
+
11
+ def self.fixnum_to_ical_day(num)
12
+ ICAL_DAYS[num]
13
+ end
14
+
15
+ def [](key)
16
+ @hash[key] ||= []
17
+ end
18
+
19
+ # Build for a single rule entry
20
+ def to_s
21
+ arr = []
22
+ if freq = @hash.delete('FREQ')
23
+ arr << "FREQ=#{freq.join(',')}"
24
+ end
25
+ arr.concat(@hash.map do |key, value|
26
+ if value.is_a?(Array)
27
+ "#{key}=#{value.join(',')}"
28
+ end
29
+ end.compact)
30
+ arr.join(';')
31
+ end
32
+
33
+ def self.ical_utc_format(time)
34
+ time = time.dup.utc
35
+ "#{time.strftime('%Y%m%dT%H%M%SZ')}" # utc time
36
+ end
37
+
38
+ def self.ical_format(time, force_utc)
39
+ time = time.dup.utc if force_utc
40
+ if time.utc?
41
+ ":#{time.strftime('%Y%m%dT%H%M%SZ')}" # utc time
42
+ else
43
+ ";TZID=#{time.strftime('%Z:%Y%m%dT%H%M%S')}" # local time specified
44
+ end
45
+ end
46
+
47
+ def self.ical_duration(duration)
48
+ hours = duration / 3600; duration %= 3600
49
+ minutes = duration / 60; duration %= 60
50
+ repr = ''
51
+ repr << "#{hours}H" if hours > 0
52
+ repr << "#{minutes}M" if minutes > 0
53
+ repr << "#{duration}S" if duration > 0
54
+ "PT#{repr}"
55
+ end
56
+
57
+ end
58
+
59
+ end
@@ -0,0 +1,74 @@
1
+ module IceCube
2
+
3
+ class StringBuilder
4
+
5
+ attr_writer :base
6
+
7
+ def initialize
8
+ @types = {}
9
+ end
10
+
11
+ def piece(type, prefix = nil, suffix = nil)
12
+ @types[type] ||= []
13
+ end
14
+
15
+ def to_s
16
+ str = @base || ''
17
+ res = @types.map do |type, segments|
18
+ if f = self.class.formatter(type)
19
+ str << ' ' + f.call(segments)
20
+ else
21
+ next if segments.empty?
22
+ str << ' ' + self.class.sentence(segments)
23
+ end
24
+ end
25
+ str
26
+ end
27
+
28
+ class << self
29
+
30
+ def formatter(type)
31
+ @formatters[type]
32
+ end
33
+
34
+ def register_formatter(type, &formatter)
35
+ @formatters ||= {}
36
+ @formatters[type] = formatter
37
+ end
38
+
39
+ end
40
+
41
+ class << self
42
+
43
+ NUMBER_SUFFIX = ['th', 'st', 'nd', 'rd', 'th', 'th', 'th', 'th', 'th', 'th']
44
+ SPECIAL_SUFFIX = { 11 => 'th', 12 => 'th', 13 => 'th', 14 => 'th' }
45
+
46
+ # influenced by ActiveSupport's to_sentence
47
+ def sentence(array)
48
+ case array.length
49
+ when 0 ; ''
50
+ when 1 ; array[0].to_s
51
+ when 2 ; "#{array[0]} and #{array[1]}"
52
+ else ; "#{array[0...-1].join(', ')}, and #{array[-1]}"
53
+ end
54
+ end
55
+
56
+ def nice_number(number)
57
+ if number == -1
58
+ 'last'
59
+ elsif number < -1
60
+ suffix = SPECIAL_SUFFIX.include?(number) ?
61
+ SPECIAL_SUFFIX[number] : NUMBER_SUFFIX[number.abs % 10]
62
+ number.abs.to_s << suffix << ' to last'
63
+ else
64
+ suffix = SPECIAL_SUFFIX.include?(number) ?
65
+ SPECIAL_SUFFIX[number] : NUMBER_SUFFIX[number.abs % 10]
66
+ number.to_s << suffix
67
+ end
68
+ end
69
+
70
+ end
71
+
72
+ end
73
+
74
+ end
@@ -0,0 +1,28 @@
1
+ module Deprecated
2
+
3
+ # Define a deprecated alias for a method
4
+ # @param [Symbol] name - name of method to define
5
+ # @param [Symbol] replacement - name of method to replace (alias)
6
+ def deprecated_alias(name, replacement)
7
+ # Create a wrapped version
8
+ define_method(name) do |*args, &block|
9
+ warn "IceCube: ##{name} deprecated (please use ##{replacement})"
10
+ send replacement, *args, &block
11
+ end
12
+ end
13
+
14
+ # Deprecate a defined method
15
+ # @param [Symbol] name - name of deprecated method
16
+ # @param [Symbol] replacement - name of the desired replacement
17
+ def deprecated(name, replacement)
18
+ # Replace old method
19
+ old_name = :"#{name}_without_deprecation"
20
+ alias_method old_name, name
21
+ # And replace it with a wrapped version
22
+ define_method(name) do |*args, &block|
23
+ warn "IceCube: ##{name} deprecated (please use ##{replacement})"
24
+ send old_name, *args, &block
25
+ end
26
+ end
27
+
28
+ end
@@ -0,0 +1,7 @@
1
+ module IceCube
2
+
3
+ # An exception for when a count on a Rule is passed
4
+ class CountExceeded < Exception
5
+ end
6
+
7
+ end
@@ -0,0 +1,7 @@
1
+ module IceCube
2
+
3
+ # An exception for when an until date on a Rule is passed
4
+ class UntilExceeded < Exception
5
+ end
6
+
7
+ end
@@ -0,0 +1,7 @@
1
+ module IceCube
2
+
3
+ # An exception for when interval is set to zero
4
+ class ZeroInterval < Exception
5
+ end
6
+
7
+ end
@@ -0,0 +1,182 @@
1
+ require 'yaml'
2
+
3
+ module IceCube
4
+
5
+ class Rule
6
+
7
+ attr_reader :uses
8
+
9
+ # Is this a terminating schedule?
10
+ def terminating?
11
+ until_time || occurrence_count
12
+ end
13
+
14
+ def ==(rule)
15
+ if rule.is_a? Rule
16
+ hash = to_hash
17
+ hash && hash == rule.to_hash
18
+ end
19
+ end
20
+
21
+ def hash
22
+ h = to_hash
23
+ h.nil? ? super : h.hash
24
+ end
25
+
26
+ # Expected to be overridden by subclasses
27
+ def to_ical
28
+ nil
29
+ end
30
+
31
+ def self.from_ical ical
32
+ params = {:validations => {}}
33
+
34
+ ical.split(';').each do |rule|
35
+ (name, value) = rule.split('=')
36
+ case name
37
+ when 'FREQ'
38
+ params[:freq] = value.downcase
39
+ when 'INTERVAL'
40
+ params[:interval] = value.to_i
41
+ when 'COUNT'
42
+ params[:count] = value.to_i
43
+ when 'UNTIL'
44
+ params[:until] = DateTime.parse(value).to_time.utc
45
+ when 'WKST'
46
+ params[:wkst] = TimeUtil.ical_day_to_symbol(value)
47
+
48
+ when 'BYSECOND'
49
+ params[:validations][:second_of_minute] = value.split(',').collect{ |v| v.to_i }
50
+ when "BYMINUTE"
51
+ params[:validations][:minute_of_hour] = value.split(',').collect{ |v| v.to_i }
52
+ when "BYHOUR"
53
+ params[:validations][:hour_of_day] = value.split(',').collect{ |v| v.to_i }
54
+ when "BYDAY"
55
+ dows = {}
56
+ days = []
57
+ value.split(',').each do |expr|
58
+ day = TimeUtil.ical_day_to_symbol(expr.strip[-2..-1])
59
+ if expr.strip.length > 2 # day with occurence
60
+ occ = expr[0..-3].to_i
61
+ dows[day].nil? ? dows[day] = [occ] : dows[day].push(occ)
62
+ days.delete(TimeUtil.symbol_to_day(day))
63
+ else
64
+ days.push TimeUtil.symbol_to_day(day) if dows[day].nil?
65
+ end
66
+ end
67
+ params[:validations][:day_of_week] = dows unless dows.empty?
68
+ params[:validations][:day] = days unless days.empty?
69
+ when "BYMONTHDAY"
70
+ params[:validations][:day_of_month] = value.split(',').collect{ |v| v.to_i }
71
+ when "BYMONTH"
72
+ params[:validations][:month_of_year] = value.split(',').collect{ |v| v.to_i }
73
+ when "BYYEARDAY"
74
+ params[:validations][:day_of_year] = value.split(',').collect{ |v| v.to_i }
75
+
76
+ else
77
+ raise "Invalid or unsupported rrule command : #{name}"
78
+ end
79
+ end
80
+
81
+ params[:interval] ||= 1
82
+ # WKST only valid for weekly rules
83
+ params.delete(:wkst) unless params[:freq] == 'weekly'
84
+
85
+ rule = IceCube::Rule.send(*params.values_at(:freq, :interval, :wkst).compact)
86
+ rule.count(params[:count]) if params[:count]
87
+ rule.until(params[:until]) if params[:until]
88
+ params[:validations].each do |key, value|
89
+ value.is_a?(Array) ? rule.send(key, *value) : rule.send(key, value)
90
+ end
91
+
92
+ rule
93
+ end
94
+
95
+ # Yaml implementation
96
+ def to_yaml(*args)
97
+ IceCube::use_psych? ? Psych::dump(to_hash) : YAML::dump(to_hash, *args)
98
+ end
99
+
100
+ # From yaml
101
+ def self.from_yaml(yaml)
102
+ from_hash IceCube::use_psych? ? Psych::load(yaml) : YAML::load(yaml)
103
+ end
104
+
105
+ # Expected to be overridden by subclasses
106
+ def to_hash
107
+ nil
108
+ end
109
+
110
+ # Convert from a hash and create a rule
111
+ def self.from_hash(hash)
112
+ return nil unless match = hash[:rule_type].match(/\:\:(.+?)Rule/)
113
+ rule = IceCube::Rule.send(match[1].downcase.to_sym, hash[:interval] || 1)
114
+ rule.until(TimeUtil.deserialize_time(hash[:until])) if hash[:until]
115
+ rule.count(hash[:count]) if hash[:count]
116
+ hash[:validations] && hash[:validations].each do |key, value|
117
+ key = key.to_sym unless key.is_a?(Symbol)
118
+ value.is_a?(::Array) ? rule.send(key, *value) : rule.send(key, value)
119
+ end
120
+ rule
121
+ end
122
+
123
+ # Reset the uses on the rule to 0
124
+ def reset
125
+ @uses = 0
126
+ end
127
+
128
+ def next_time(time, schedule, closing_time)
129
+ end
130
+
131
+ def on?(time, schedule)
132
+ next_time(time, schedule, time) == time
133
+ end
134
+
135
+ # Whether this rule requires a full run
136
+ def full_required?
137
+ !@count.nil?
138
+ end
139
+
140
+ # Convenience methods for creating Rules
141
+ class << self
142
+
143
+ # Secondly Rule
144
+ def secondly(interval = 1)
145
+ SecondlyRule.new(interval)
146
+ end
147
+
148
+ # Minutely Rule
149
+ def minutely(interval = 1)
150
+ MinutelyRule.new(interval)
151
+ end
152
+
153
+ # Hourly Rule
154
+ def hourly(interval = 1)
155
+ HourlyRule.new(interval)
156
+ end
157
+
158
+ # Daily Rule
159
+ def daily(interval = 1)
160
+ DailyRule.new(interval)
161
+ end
162
+
163
+ # Weekly Rule
164
+ def weekly(interval = 1, week_start = :sunday)
165
+ WeeklyRule.new(interval, week_start)
166
+ end
167
+
168
+ # Monthly Rule
169
+ def monthly(interval = 1)
170
+ MonthlyRule.new(interval)
171
+ end
172
+
173
+ # Yearly Rule
174
+ def yearly(interval = 1)
175
+ YearlyRule.new(interval)
176
+ end
177
+
178
+ end
179
+
180
+ end
181
+
182
+ end