ice_cube_conrad 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
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