recurs 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/Gemfile +9 -0
- data/README +17 -0
- data/Rakefile +2 -0
- data/lib/recurs.rb +245 -0
- data/lib/recurs/consts.rb +40 -0
- data/lib/recurs/rules.rb +92 -0
- data/lib/recurs/version.rb +3 -0
- data/lib/util/module.rb +67 -0
- data/recurs.gemspec +22 -0
- data/spec/models/event_spec.rb +66 -0
- data/spec/models/recurrence_spec.rb +128 -0
- data/spec/models/rules_spec.rb +40 -0
- data/spec/spec_helper.rb +4 -0
- metadata +95 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# Recurs
|
2
|
+
Is a small Recurring Event parsing library. It will take symbolized input and output icalendar standard recurring events,
|
3
|
+
see the specs for examples of the api
|
4
|
+
|
5
|
+
This gem has been tested as a standalone in ruby 1.9, or using Rails 3
|
6
|
+
|
7
|
+
It also allows the developer to specifiy specialized Recurrence schemes by using the power of Ruby closures
|
8
|
+
|
9
|
+
## Rails (ActiveRecord)
|
10
|
+
|
11
|
+
class Event < AR
|
12
|
+
acts_as_recurring
|
13
|
+
end
|
14
|
+
|
15
|
+
|
16
|
+
## TODO
|
17
|
+
Finish the instance functionality
|
data/Rakefile
ADDED
data/lib/recurs.rb
ADDED
@@ -0,0 +1,245 @@
|
|
1
|
+
require 'util/module' unless defined? Rails
|
2
|
+
require 'recurs/consts'
|
3
|
+
require 'recurs/rules'
|
4
|
+
require 'ri_cal'
|
5
|
+
module Recurs
|
6
|
+
include RiCal
|
7
|
+
# Your code goes here...
|
8
|
+
|
9
|
+
module Parser
|
10
|
+
|
11
|
+
def self.included(base)
|
12
|
+
base.send :extend, ClassMethods
|
13
|
+
end
|
14
|
+
|
15
|
+
# class << self is NOT the same as including ( seen above ) which makes methods available to CLASSES that include the module
|
16
|
+
#, it ONLY makes the methods available to the MODULE itself
|
17
|
+
class << self
|
18
|
+
def rrule(repeats=nil, args={})
|
19
|
+
args[:rule] = :r
|
20
|
+
rule(repeats, args)
|
21
|
+
end
|
22
|
+
|
23
|
+
def exrule(repeats=nil, args={})
|
24
|
+
args[:rule] = :e
|
25
|
+
rule(repeats, args)
|
26
|
+
end
|
27
|
+
|
28
|
+
def rdate(dates=nil, args={})
|
29
|
+
args[:rule] = :r
|
30
|
+
date(dates, args)
|
31
|
+
end
|
32
|
+
|
33
|
+
def exdate(dates=nil, args={})
|
34
|
+
args[:rule] = :e
|
35
|
+
date(dates, args)
|
36
|
+
end
|
37
|
+
|
38
|
+
def date(dates=nil, args={})
|
39
|
+
args[:rule] == :r ? r = "RDATE" : r = "EXDATE"
|
40
|
+
if dates.is_a? Hash
|
41
|
+
f_dates = dates.flatten
|
42
|
+
if (f_dates[0] == :period) || (f_dates[0] == :range)
|
43
|
+
r += ";VALUE=PERIOD:"
|
44
|
+
i = 0
|
45
|
+
l = f_dates[1].length
|
46
|
+
e = 1
|
47
|
+
f_dates[1].each { |d|
|
48
|
+
r += RiCal::FastDateTime.from_date_time(d.to_datetime).ical_str
|
49
|
+
r += '/' if i == 0
|
50
|
+
r += ',' if (e < l) && (i != 0)
|
51
|
+
i += 1
|
52
|
+
e += 1
|
53
|
+
}
|
54
|
+
elsif f_dates[0] == :dates
|
55
|
+
r += ":"
|
56
|
+
l = f_dates[1].length
|
57
|
+
e = 1
|
58
|
+
f_dates[1].each {|d|
|
59
|
+
r += "#{RiCal::FastDateTime.from_date_time(d.to_datetime).ical_str}"
|
60
|
+
r += "," if (e < l)
|
61
|
+
e += 1
|
62
|
+
}
|
63
|
+
end
|
64
|
+
elsif dates.is_a?(Date) || dates.is_a?(DateTime)
|
65
|
+
r += ":#{RiCal::FastDateTime.from_date_time(dates.to_datetime).ical_str}"
|
66
|
+
end
|
67
|
+
r
|
68
|
+
end
|
69
|
+
|
70
|
+
protected
|
71
|
+
@@rule = nil
|
72
|
+
|
73
|
+
# The BYSECOND attr could be eg: 14 or multiple: 14, 45 between 0 and 59 ( i assume )
|
74
|
+
# The BYMINUTE attr could be eg: 14 or multiple: 14, 45 between 0 and 59 ( i assume )
|
75
|
+
# The BYSECOND attr could be eg: 14 or multiple: 14, 22 between 0 and 23 ( i assume )
|
76
|
+
# The BYDAY attribute allows you to specify exactly which days (SA, SU, MO, TU, WE, TH, FR)
|
77
|
+
# The BYMONTH is any month value between 1 .. 12
|
78
|
+
# the BYMONTHDAY is any value between 1 and 31
|
79
|
+
# WKST is the week starting on BYDAY eg SU,MO,TU,WE,TH
|
80
|
+
|
81
|
+
#args.each {|a| args[a[0]] = a[1].to_s.upcase if (a[1].is_a? String) || (a[1].is_a? Symbol)}
|
82
|
+
|
83
|
+
|
84
|
+
def rule(repeats, args = {})
|
85
|
+
args[:rule] == :r ? @@rule = "RRULE" : @@rule = "EXRULE"
|
86
|
+
@@rule += ":FREQ=#{repeats.to_s.upcase}"
|
87
|
+
interval(args)
|
88
|
+
args.each { |ar|
|
89
|
+
unless [:rule, :by_set_pos, :by_week_at, :count, :occurrences, :until, :ends_at, :repeats_every, :interval].include? ar[0]
|
90
|
+
@@rule += ";#{ar[0].to_s.gsub('_', '').upcase}=#{by_unit(ar[0], ar[1])}"
|
91
|
+
end
|
92
|
+
}
|
93
|
+
@@rule += ";BYSETPOS=#{args[:by_set_pos]}" if args[:by_set_pos]
|
94
|
+
@@rule += ";WKST=#{args[:by_week_st]}" if args[:by_week_st]
|
95
|
+
ending(args)
|
96
|
+
@@rule
|
97
|
+
end
|
98
|
+
|
99
|
+
def by_unit(measure, units)
|
100
|
+
ms = {:by_second => [60, get_num, BY_N_SECONDS],
|
101
|
+
:by_minute => [60, get_num, BY_N_MINUTES],
|
102
|
+
:by_hour => [23, get_num, BY_N_HOURS],
|
103
|
+
:by_day => [7, get_day_num, BY_DAYS],
|
104
|
+
:by_month_day => [31, get_day_num, BY_N_MONTH_DAYS],
|
105
|
+
:by_year_day => [366, get_day_num, BY_N_YEAR_DAYS],
|
106
|
+
:by_week => [52, get_num, BY_N_WEEKS],
|
107
|
+
:by_month => [12, get_month_num, BY_N_MONTHS]}
|
108
|
+
|
109
|
+
@measure = ms[measure]
|
110
|
+
|
111
|
+
r = ""
|
112
|
+
d = get_unit_nums(units)
|
113
|
+
comp = d.uniq.compact
|
114
|
+
c = comp.count
|
115
|
+
z = 0
|
116
|
+
comp.each { |i|
|
117
|
+
z += 1
|
118
|
+
r += @measure[2][i].to_s
|
119
|
+
r += "," unless z == c
|
120
|
+
}
|
121
|
+
r
|
122
|
+
end
|
123
|
+
|
124
|
+
def get_unit_nums(units)
|
125
|
+
r = []
|
126
|
+
# consts DAYS, BY_DAY
|
127
|
+
if units.is_a? Array
|
128
|
+
units.each { |d|
|
129
|
+
r << @measure[1].call(d)
|
130
|
+
}
|
131
|
+
else
|
132
|
+
r << @measure[1].call(units)
|
133
|
+
end
|
134
|
+
r
|
135
|
+
end
|
136
|
+
|
137
|
+
def get_num
|
138
|
+
->(num){
|
139
|
+
num.to_i.modulo(@measure[0]) unless num.is_a? Symbol;
|
140
|
+
}
|
141
|
+
end
|
142
|
+
|
143
|
+
def get_day_num
|
144
|
+
->(day){
|
145
|
+
if DAYS.include?(day.to_s.capitalize)
|
146
|
+
DAYS.find_index(day.to_s.capitalize)
|
147
|
+
elsif BY_DAYS.include?(day.to_s.upcase)
|
148
|
+
BY_DAYS.find_index(day.to_s.upcase)
|
149
|
+
else
|
150
|
+
day.to_i.modulo(7) unless day.is_a? Symbol
|
151
|
+
end;
|
152
|
+
}
|
153
|
+
end
|
154
|
+
|
155
|
+
def get_month_num
|
156
|
+
->(mon){
|
157
|
+
if MONTHS.include?(mon.to_s.capitalize)
|
158
|
+
MONTHS.find_index(mon.to_s.capitalize)
|
159
|
+
elsif BY_N_MONTHS.include?(mon.to_s.upcase)
|
160
|
+
BY_N_MONTHS.find_index(mon.to_s.upcase)
|
161
|
+
else
|
162
|
+
mon.to_i.modulo(12) unless mon.is_a? Symbol
|
163
|
+
end;
|
164
|
+
}
|
165
|
+
end
|
166
|
+
|
167
|
+
def interval(args)
|
168
|
+
if args[:repeats_every]
|
169
|
+
@@rule += ";INTERVAL=#{args[:repeats_every]}"
|
170
|
+
elsif args[:interval]
|
171
|
+
@@rule += ";INTERVAL=#{args[:interval]}"
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
def ending(args)
|
176
|
+
if args[:count]
|
177
|
+
@@rule += ";COUNT=#{args[:count]}"
|
178
|
+
elsif args[:occurrences]
|
179
|
+
@@rule += ";COUNT=#{args[:occurrences]}"
|
180
|
+
elsif args[:until]
|
181
|
+
@@rule += ";UNTIL=#{RiCal::FastDateTime.from_date_time(args[:until].to_datetime).ical_str}"
|
182
|
+
elsif args[:ends_at]
|
183
|
+
@@rule += ";UNTIL=#{RiCal::FastDateTime.from_date_time(args[:ends_at].to_datetime).ical_str}"
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
end
|
188
|
+
|
189
|
+
module ClassMethods
|
190
|
+
def acts_as_recurring
|
191
|
+
send :include, InstanceMethods
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
module InstanceMethods
|
196
|
+
def initialize
|
197
|
+
@rrules ||= []
|
198
|
+
@exrules ||= []
|
199
|
+
@rdates ||= []
|
200
|
+
@exdates ||= []
|
201
|
+
super
|
202
|
+
end
|
203
|
+
|
204
|
+
def recurs
|
205
|
+
r = @rrules
|
206
|
+
r.concat @exrules
|
207
|
+
r.concat @rdates
|
208
|
+
r.concat @exdates
|
209
|
+
r.join
|
210
|
+
end
|
211
|
+
|
212
|
+
def add_rrule(repeats, args = {})
|
213
|
+
rrule = Parser.rrule(repeats, args)
|
214
|
+
@rrules << rrule
|
215
|
+
rrule
|
216
|
+
end
|
217
|
+
|
218
|
+
def add_exrule(repeats, args = {})
|
219
|
+
exrule = Parser.exrule(repeats, args)
|
220
|
+
@exrules << exrule
|
221
|
+
exrule
|
222
|
+
end
|
223
|
+
|
224
|
+
def add_rdate(args = {})
|
225
|
+
rdate = Parser.rdate(args)
|
226
|
+
@rdates << rdate
|
227
|
+
rdate
|
228
|
+
end
|
229
|
+
|
230
|
+
def add_exdate(args = {})
|
231
|
+
exdate = Parser.exdate(args)
|
232
|
+
@exdates << exdate
|
233
|
+
exdate
|
234
|
+
end
|
235
|
+
|
236
|
+
protected
|
237
|
+
attr_accessor :rrules, :exrules, :rdates, :exdates
|
238
|
+
end
|
239
|
+
|
240
|
+
|
241
|
+
end
|
242
|
+
|
243
|
+
|
244
|
+
end
|
245
|
+
ActiveRecord::Base.send(:include, Recurs::Parser) if defined? Rails
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Recurs
|
2
|
+
#module Consts
|
3
|
+
# 0 1 2 3 4 5 6
|
4
|
+
DAYS = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']
|
5
|
+
MONTHS = ['January', "February", 'March', 'April', 'May', 'June',
|
6
|
+
'July', 'August', 'September', 'October', 'November', 'December']
|
7
|
+
BY_N_SECONDS = (0..59).to_a
|
8
|
+
BY_N_MINUTES = (0..59).to_a
|
9
|
+
BY_N_HOURS = (0..23).to_a
|
10
|
+
BY_N_DAYS = (0..6).to_a
|
11
|
+
BY_N_MONTH_DAYS = (1..31).to_a
|
12
|
+
BY_N_YEAR_DAYS = (1..366).to_a
|
13
|
+
BY_DAYS = ['SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA']
|
14
|
+
BY_N_WEEKS = (1..54).to_a
|
15
|
+
BY_N_MONTHS = (1..12).to_a
|
16
|
+
BY_MONTHS = ['jan', 'feb', 'mar', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec']
|
17
|
+
|
18
|
+
#SYM_DAYS = [:sunday, :monday, :tuesday, :wednesday, :thursday, :friday, :saturday]
|
19
|
+
SYM_INTEGERS = [:zero, :one, :two, :three, :four, :five, :six, :seven, :eight, :nine, :ten]
|
20
|
+
SYM_TEENS = [:eleven, :twelve, :thirteen, :fourteen, :fifteen, :sixteen, :seventeen, :eighteen, :nineteen]
|
21
|
+
SYM_INTEGER_GROUP = SYM_INTEGERS+SYM_TEENS
|
22
|
+
|
23
|
+
# SYM_TENS[(34 - 34.modulo(10))/10]
|
24
|
+
SYM_TENS = [:ten, :twenty, :thirty, :fourty, :fifty, :sixty, :seventy, :eighty, :ninety, :hundred]
|
25
|
+
|
26
|
+
def get_integer_from_sym(sym)
|
27
|
+
SYM_INTEGER_GROUP(sym)
|
28
|
+
end
|
29
|
+
|
30
|
+
def build_num_sym(int)
|
31
|
+
if int > 19
|
32
|
+
r = int.modulo(10)
|
33
|
+
t = int - r
|
34
|
+
[SYM_TENS(t), SYM_INTEGERS[r]]
|
35
|
+
else
|
36
|
+
SYM_INTEGER_GROUP[int]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
#end
|
40
|
+
end
|
data/lib/recurs/rules.rb
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
module Recurs
|
2
|
+
module Rules
|
3
|
+
mattr_accessor :repeat_procs, :schema
|
4
|
+
@@repeat_procs = {
|
5
|
+
|
6
|
+
=begin
|
7
|
+
'Daily' => ->(args = {}){
|
8
|
+
args[:repeats] = 'DAILY'
|
9
|
+
rrule(args);
|
10
|
+
}, # 0
|
11
|
+
=end
|
12
|
+
|
13
|
+
#=begin
|
14
|
+
'Daily' => ->(set=false, args = {}){
|
15
|
+
unless set
|
16
|
+
@recurrence_template = ['standard','Days']
|
17
|
+
else
|
18
|
+
Parser.rrule(:daily, args)
|
19
|
+
end;
|
20
|
+
}, # 0
|
21
|
+
#=end
|
22
|
+
|
23
|
+
'Every Weekday ( Mon - Fri )' => ->(set=false, args = {}){
|
24
|
+
unless set
|
25
|
+
@recurrence_template = 'set_points'
|
26
|
+
else
|
27
|
+
args[:by_day] = [1,2,3,4,5]
|
28
|
+
Parser.rrule(:weekly, args)
|
29
|
+
#
|
30
|
+
# .
|
31
|
+
end;
|
32
|
+
}, # 1
|
33
|
+
|
34
|
+
'Every Mon, Wed, Fri' => ->(set=false, args = {}){
|
35
|
+
unless set
|
36
|
+
@recurrence_template = 'set_points'
|
37
|
+
else
|
38
|
+
args[:by_day] = [1,3,5]
|
39
|
+
Parser.rrule(:weekly, args)
|
40
|
+
end;
|
41
|
+
}, # 2
|
42
|
+
|
43
|
+
'Every Tues, Thurs' => ->(set=false, args = {}){
|
44
|
+
unless set
|
45
|
+
@recurrence_template = 'set_points'
|
46
|
+
else
|
47
|
+
args[:by_day] = [2,4]
|
48
|
+
Parser.rrule(:weekly, args)
|
49
|
+
end;
|
50
|
+
}, # 3
|
51
|
+
|
52
|
+
'Every Weekend' => ->(set=false, args = {}){
|
53
|
+
unless set
|
54
|
+
@recurrence_template = 'set_points'
|
55
|
+
else
|
56
|
+
args[:by_day] = [0,6]
|
57
|
+
Parser.rrule(:weekly, args)
|
58
|
+
end;
|
59
|
+
}, # 4
|
60
|
+
|
61
|
+
'Weekly' => ->(set=false, args = {}){
|
62
|
+
unless set
|
63
|
+
@recurrence_template = ['weekly', 'Weeks']
|
64
|
+
else
|
65
|
+
Parser.rrule(:weekly, args)
|
66
|
+
end;
|
67
|
+
}, # 5
|
68
|
+
|
69
|
+
'Monthly' => ->(set=false, args = {}){
|
70
|
+
unless set
|
71
|
+
@recurrence_template = ['monthly', 'Months']
|
72
|
+
else
|
73
|
+
Parser.rrule(:monthly, args)
|
74
|
+
end;
|
75
|
+
}, # 6
|
76
|
+
|
77
|
+
'Yearly' => ->(set=false, args = {}){
|
78
|
+
unless set
|
79
|
+
@recurrence_template = ['standard', 'Years']
|
80
|
+
else
|
81
|
+
Parser.rrule(:yearly, args)
|
82
|
+
end;
|
83
|
+
} # 7
|
84
|
+
|
85
|
+
}
|
86
|
+
class << self
|
87
|
+
def schemes
|
88
|
+
@@schemas = @@repeat_procs.keys if @@repeat_procs.is_a? Hash
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
data/lib/util/module.rb
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
# these utility methods are taken from Ruby On Rails ActiveSupport module
|
2
|
+
|
3
|
+
class Array
|
4
|
+
# Extracts options from a set of arguments. Removes and returns the last
|
5
|
+
# element in the array if it's a hash, otherwise returns a blank hash.
|
6
|
+
#
|
7
|
+
# def options(*args)
|
8
|
+
# args.extract_options!
|
9
|
+
# end
|
10
|
+
#
|
11
|
+
# options(1, 2) # => {}
|
12
|
+
# options(1, 2, :a => :b) # => {:a=>:b}
|
13
|
+
def extract_options!
|
14
|
+
if last.is_a?(Hash) && last.extractable_options?
|
15
|
+
pop
|
16
|
+
else
|
17
|
+
{}
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class Module
|
23
|
+
def mattr_reader(*syms)
|
24
|
+
syms.each do |sym|
|
25
|
+
next if sym.is_a?(Hash)
|
26
|
+
class_eval(<<-EOS, __FILE__, __LINE__)
|
27
|
+
unless defined? @@#{sym}
|
28
|
+
@@#{sym} = nil
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.#{sym}
|
32
|
+
@@#{sym}
|
33
|
+
end
|
34
|
+
|
35
|
+
def #{sym}
|
36
|
+
@@#{sym}
|
37
|
+
end
|
38
|
+
EOS
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def mattr_writer(*syms)
|
43
|
+
options = syms.extract_options!
|
44
|
+
syms.each do |sym|
|
45
|
+
class_eval(<<-EOS, __FILE__, __LINE__)
|
46
|
+
unless defined? @@#{sym}
|
47
|
+
@@#{sym} = nil
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.#{sym}=(obj)
|
51
|
+
@@#{sym} = obj
|
52
|
+
end
|
53
|
+
|
54
|
+
#{"
|
55
|
+
def #{sym}=(obj)
|
56
|
+
@@#{sym} = obj
|
57
|
+
end
|
58
|
+
" unless options[:instance_writer] == false }
|
59
|
+
EOS
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def mattr_accessor(*syms)
|
64
|
+
mattr_reader(*syms)
|
65
|
+
mattr_writer(*syms)
|
66
|
+
end
|
67
|
+
end
|
data/recurs.gemspec
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "recurs/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "recurs"
|
7
|
+
s.version = Recurs::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Steve Caney Martin"]
|
10
|
+
s.email = ["steve@shakewell.co.uk"]
|
11
|
+
s.homepage = "http://rubygems.org/gems/recurs"
|
12
|
+
s.summary = %q{A recurrence generator for ical format}
|
13
|
+
s.description = %q{Specifiy you're recurrence pattern in symbols and strings and get an ical format recurrence string}
|
14
|
+
|
15
|
+
s.rubyforge_project = "recurs"
|
16
|
+
s.add_dependency('ri_cal', '>= 0.8.7')
|
17
|
+
|
18
|
+
s.files = `git ls-files`.split("\n")
|
19
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
20
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
21
|
+
s.require_paths = ["lib"]
|
22
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
class Event;end
|
4
|
+
Event.send(:include, Recurs::Parser)
|
5
|
+
class Event
|
6
|
+
acts_as_recurring
|
7
|
+
end
|
8
|
+
|
9
|
+
describe Event do
|
10
|
+
before :each do
|
11
|
+
@event = Event.new
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should act as recurring" do
|
15
|
+
#@event.rrules
|
16
|
+
#@event.exrules
|
17
|
+
#@event.rdates
|
18
|
+
#@event.exdates
|
19
|
+
@event.recurs
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should add an rrule" do
|
23
|
+
@event.add_rrule(:daily).should == "RRULE:FREQ=DAILY"
|
24
|
+
@event.recurs.should == "RRULE:FREQ=DAILY"
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should add an exrule" do
|
28
|
+
@event.add_exrule(:daily).should == "EXRULE:FREQ=DAILY"
|
29
|
+
@event.recurs.should == "EXRULE:FREQ=DAILY"
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should add an rdate" do
|
33
|
+
@event.add_rdate(Date.today).should == "RDATE:#{RiCal::FastDateTime.from_date_time(Date.today.to_datetime).ical_str}"
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should add an rdate range" do
|
37
|
+
@event.add_rdate(:period => [Date.today, (Date.today+2)]).should == "RDATE;VALUE=PERIOD:#{RiCal::FastDateTime.from_date_time(Date.today.to_datetime).ical_str}/#{RiCal::FastDateTime.from_date_time((Date.today+2).to_datetime).ical_str}"
|
38
|
+
end
|
39
|
+
it "should add a list of rdates" do
|
40
|
+
@event.add_rdate(:dates => [Date.today, (Date.today+2)]).should == "RDATE:#{RiCal::FastDateTime.from_date_time(Date.today.to_datetime).ical_str},#{RiCal::FastDateTime.from_date_time((Date.today+2).to_datetime).ical_str}"
|
41
|
+
end
|
42
|
+
it "should add an exdate" do
|
43
|
+
@event.add_exdate(Date.today).should == "EXDATE:#{RiCal::FastDateTime.from_date_time(Date.today.to_datetime).ical_str}"
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
=begin
|
49
|
+
An event has recurrence
|
50
|
+
|
51
|
+
@event.recurs #"DTSTART;TZID=US-Eastern:19970902T0900
|
52
|
+
RRULE:FREQ=DAILY;COUNT=10
|
53
|
+
EXRULE:"
|
54
|
+
|
55
|
+
TODO: Ascertain how ri_cal parses complex rules
|
56
|
+
|
57
|
+
While building the recurrence the event must use private instance attributes from the recurrrence module:
|
58
|
+
|
59
|
+
The recurrence module must be mixed into the model transparently, either by using an acts_as
|
60
|
+
or by inheriting from the module
|
61
|
+
|
62
|
+
class Event < AR
|
63
|
+
acts_as_recurring_event
|
64
|
+
end
|
65
|
+
|
66
|
+
=end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Recurs do
|
4
|
+
|
5
|
+
end
|
6
|
+
|
7
|
+
describe Recurs::Parser do
|
8
|
+
#pending "add some examples to (or delete) #{__FILE__}"
|
9
|
+
|
10
|
+
it "should respond to class/module methods:" do
|
11
|
+
Recurs::Parser.rrule
|
12
|
+
Recurs::Parser.exrule
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should get basic rrule" do
|
16
|
+
Recurs::Parser.rrule.should == "RRULE:FREQ="
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should get basic exrule" do
|
20
|
+
Recurs::Parser.exrule.should == "EXRULE:FREQ="
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should build a basic daily rule" do
|
24
|
+
Recurs::Parser.rrule(:daily).should == "RRULE:FREQ=DAILY"
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should create a daily recurrence with a two day interval" do
|
28
|
+
Recurs::Parser.rrule(:daily, :interval => 2).should == "RRULE:FREQ=DAILY;INTERVAL=2"
|
29
|
+
Recurs::Parser.rrule(:daily, :repeats_every => 2).should == "RRULE:FREQ=DAILY;INTERVAL=2"
|
30
|
+
Recurs::Parser.rrule(:daily, :repeats_every => 2, :interval => 2).should == "RRULE:FREQ=DAILY;INTERVAL=2"
|
31
|
+
end
|
32
|
+
#=begin
|
33
|
+
it "should create a daily recurrence with a two day interval for ten occurrences" do
|
34
|
+
Recurs::Parser.rrule(:daily, :interval => 2, :count => 10).should == "RRULE:FREQ=DAILY;INTERVAL=2;COUNT=10"
|
35
|
+
Recurs::Parser.rrule(:daily, :interval => 2, :occurrences => 10).should == "RRULE:FREQ=DAILY;INTERVAL=2;COUNT=10"
|
36
|
+
Recurs::Parser.rrule(:daily, :interval => 2, :count => 10, :occurrences => 10).should == "RRULE:FREQ=DAILY;INTERVAL=2;COUNT=10"
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should create a valid ending, 'count' OR 'until' NOT both, PREFER 'count'" do
|
40
|
+
Recurs::Parser.rrule(:daily, :interval => 2, :count => 10).should == "RRULE:FREQ=DAILY;INTERVAL=2;COUNT=10"
|
41
|
+
Recurs::Parser.rrule(:daily, :interval => 2, :ends_at => Date.today).should == "RRULE:FREQ=DAILY;INTERVAL=2;UNTIL=#{RiCal::FastDateTime.from_date_time(Date.today.to_datetime).ical_str}"
|
42
|
+
Recurs::Parser.rrule(:daily, :interval => 2, :until => Date.today).should == "RRULE:FREQ=DAILY;INTERVAL=2;UNTIL=#{RiCal::FastDateTime.from_date_time(Date.today.to_datetime).ical_str}"
|
43
|
+
Recurs::Parser.rrule(:daily, :interval => 2, :until => Date.today, :count => 10).should == "RRULE:FREQ=DAILY;INTERVAL=2;COUNT=10"
|
44
|
+
end
|
45
|
+
#=end
|
46
|
+
it "should create a weekly occurrence" do
|
47
|
+
Recurs::Parser.rrule(:weekly).should == "RRULE:FREQ=WEEKLY"
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should build a basic daily rule with a count" do
|
51
|
+
Recurs::Parser.rrule(:daily, :count => 10).should == "RRULE:FREQ=DAILY;COUNT=10"
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should create a weekly occurrence on monday and thursday" do
|
55
|
+
Recurs::Parser.rrule(:weekly, :by_day => [1, 4]).should == "RRULE:FREQ=WEEKLY;BYDAY=MO,TH"
|
56
|
+
Recurs::Parser.rrule(:weekly, :by_day => [1, '4']).should == "RRULE:FREQ=WEEKLY;BYDAY=MO,TH"
|
57
|
+
Recurs::Parser.rrule(:weekly, :by_day => ['MO', 'TH']).should == "RRULE:FREQ=WEEKLY;BYDAY=MO,TH"
|
58
|
+
Recurs::Parser.rrule(:weekly, :by_day => ['Monday', 'THURSDAY']).should == "RRULE:FREQ=WEEKLY;BYDAY=MO,TH"
|
59
|
+
Recurs::Parser.rrule(:weekly, :by_day => [1, 'TH']).should == "RRULE:FREQ=WEEKLY;BYDAY=MO,TH"
|
60
|
+
Recurs::Parser.rrule(:weekly, :by_day => [2, 'TH']).should_not == "RRULE:FREQ=WEEKLY;BYDAY=MO,TH"
|
61
|
+
|
62
|
+
#Unique
|
63
|
+
Recurs::Parser.rrule(:weekly, :by_day => [1, 1, 4, 'TH', 'Thursday', 'MO']).should == "RRULE:FREQ=WEEKLY;BYDAY=MO,TH"
|
64
|
+
end
|
65
|
+
|
66
|
+
it "should create a weekly occurrence for three weeks" do
|
67
|
+
Recurs::Parser.rrule(:weekly, :count => 3).should == "RRULE:FREQ=WEEKLY;COUNT=3"
|
68
|
+
end
|
69
|
+
|
70
|
+
it "should create a weekly occurrence for three weeks on fridays" do
|
71
|
+
Recurs::Parser.rrule(:weekly, :count => 3, :by_day => 'FR').should == "RRULE:FREQ=WEEKLY;BYDAY=FR;COUNT=3"
|
72
|
+
Recurs::Parser.rrule(:weekly, :count => 3, :by_day => :friday).should == "RRULE:FREQ=WEEKLY;BYDAY=FR;COUNT=3"
|
73
|
+
end
|
74
|
+
|
75
|
+
it "should create a weekly occurrence for three weeks on fridays and sundays" do
|
76
|
+
Recurs::Parser.rrule(:weekly, :count => 3, :by_day => ['FR', 0]).should == "RRULE:FREQ=WEEKLY;BYDAY=FR,SU;COUNT=3"
|
77
|
+
Recurs::Parser.rrule(:weekly, :count => 3, :by_day => [:friday, 0]).should == "RRULE:FREQ=WEEKLY;BYDAY=FR,SU;COUNT=3"
|
78
|
+
end
|
79
|
+
|
80
|
+
it "should bypass badly input days and use good values" do
|
81
|
+
Recurs::Parser.rrule(:weekly, :count => 3, :by_day => ['fer', 0]).should == "RRULE:FREQ=WEEKLY;BYDAY=SU;COUNT=3"
|
82
|
+
Recurs::Parser.rrule(:weekly, :count => 3, :by_day => [:fer, 0]).should == "RRULE:FREQ=WEEKLY;BYDAY=SU;COUNT=3"
|
83
|
+
Recurs::Parser.rrule(:weekly, :count => 3, :by_day => [:fesefra, :sunday]).should == "RRULE:FREQ=WEEKLY;BYDAY=SU;COUNT=3"
|
84
|
+
end
|
85
|
+
|
86
|
+
it "should occur every month" do
|
87
|
+
Recurs::Parser.rrule(:monthly).should == "RRULE:FREQ=MONTHLY"
|
88
|
+
end
|
89
|
+
|
90
|
+
it "should occur every year" do
|
91
|
+
Recurs::Parser.rrule(:yearly).should == "RRULE:FREQ=YEARLY"
|
92
|
+
end
|
93
|
+
|
94
|
+
it "should occur every january of each year" do
|
95
|
+
Recurs::Parser.rrule(:yearly, :by_month => :january).should == "RRULE:FREQ=YEARLY;BYMONTH=1"
|
96
|
+
end
|
97
|
+
|
98
|
+
it "should occur on every thursday of every janruary of each year" do
|
99
|
+
Recurs::Parser.rrule(:yearly, :by_month => :january, :by_day => :thursday).should == "RRULE:FREQ=YEARLY;BYMONTH=1;BYDAY=TH"
|
100
|
+
end
|
101
|
+
|
102
|
+
it "should have independent instance rules" do
|
103
|
+
Recurs::Parser.rrule(:yearly, :by_month => :january).should == "RRULE:FREQ=YEARLY;BYMONTH=1"
|
104
|
+
Recurs::Parser.rrule(:weekly, :count => 3, :by_day => ['fer', 0]).should == "RRULE:FREQ=WEEKLY;BYDAY=SU;COUNT=3"
|
105
|
+
Recurs::Parser.rrule(:yearly, :by_month => :january).should == "RRULE:FREQ=YEARLY;BYMONTH=1"
|
106
|
+
end
|
107
|
+
|
108
|
+
end
|
109
|
+
|
110
|
+
|
111
|
+
=begin
|
112
|
+
# A recurrence instance should implement a couple of attributes ( recurs, rrules, rdates, exrules )
|
113
|
+
|
114
|
+
Recurs::Parser.recurs is a composite method combining all rrules and exrules
|
115
|
+
Recurs::Parser.rrules is an array of rrules similarly exrules is the same
|
116
|
+
|
117
|
+
it "instance should respond to instance methods:" do
|
118
|
+
Recurs::Parser = Parser.new
|
119
|
+
Recurs::Parser.recurs
|
120
|
+
Recurs::Parser.rrules
|
121
|
+
end
|
122
|
+
|
123
|
+
# The Parser Class should implement singular versions ( rrule, exrule ), these methods generate atomic rules
|
124
|
+
|
125
|
+
The Parser class implements the rrule and exrule methods => alias_method_chain perhaps
|
126
|
+
( these are actually just aliases of the same method but with predefined flags )
|
127
|
+
|
128
|
+
=end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
describe Recurs::Rules do
|
3
|
+
it "should respond to class/module variables" do
|
4
|
+
Recurs::Rules.repeat_procs
|
5
|
+
Recurs::Rules.schemes
|
6
|
+
end
|
7
|
+
|
8
|
+
it "should build Daily" do
|
9
|
+
Recurs::Rules.repeat_procs['Daily'].call(true, :count => 10).should == "RRULE:FREQ=DAILY;COUNT=10"
|
10
|
+
end
|
11
|
+
#=begin
|
12
|
+
it 'Every Weekday ( Mon - Fri )' do
|
13
|
+
Recurs::Rules.repeat_procs['Every Weekday ( Mon - Fri )'].call(true, :count => 10).should == "RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR;COUNT=10"
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'Every Mon, Wed, Fri' do
|
17
|
+
Recurs::Rules.repeat_procs['Every Mon, Wed, Fri'].call(true, :count => 10).should == "RRULE:FREQ=WEEKLY;BYDAY=MO,WE,FR;COUNT=10"
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'Every Tues, Thurs' do
|
21
|
+
Recurs::Rules.repeat_procs['Every Tues, Thurs'].call(true, :count => 10).should == "RRULE:FREQ=WEEKLY;BYDAY=TU,TH;COUNT=10"
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'Every Weekend' do
|
25
|
+
Recurs::Rules.repeat_procs['Every Weekend'].call(true, :count => 10).should == "RRULE:FREQ=WEEKLY;BYDAY=SU,SA;COUNT=10"
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'Weekly' do
|
29
|
+
Recurs::Rules.repeat_procs['Weekly'].call(true, :count => 10).should == "RRULE:FREQ=WEEKLY;COUNT=10"
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'Monthly' do
|
33
|
+
Recurs::Rules.repeat_procs['Monthly'].call(true, :count => 10).should == "RRULE:FREQ=MONTHLY;COUNT=10"
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'Yearly' do
|
37
|
+
Recurs::Rules.repeat_procs['Yearly'].call(true, :count => 10).should == "RRULE:FREQ=YEARLY;COUNT=10"
|
38
|
+
end
|
39
|
+
#=end
|
40
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: recurs
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
version: 0.0.1
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Steve Caney Martin
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-12-14 00:00:00 +00:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: ri_cal
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
segments:
|
29
|
+
- 0
|
30
|
+
- 8
|
31
|
+
- 7
|
32
|
+
version: 0.8.7
|
33
|
+
type: :runtime
|
34
|
+
version_requirements: *id001
|
35
|
+
description: Specifiy you're recurrence pattern in symbols and strings and get an ical format recurrence string
|
36
|
+
email:
|
37
|
+
- steve@shakewell.co.uk
|
38
|
+
executables: []
|
39
|
+
|
40
|
+
extensions: []
|
41
|
+
|
42
|
+
extra_rdoc_files: []
|
43
|
+
|
44
|
+
files:
|
45
|
+
- .gitignore
|
46
|
+
- Gemfile
|
47
|
+
- README
|
48
|
+
- Rakefile
|
49
|
+
- lib/recurs.rb
|
50
|
+
- lib/recurs/consts.rb
|
51
|
+
- lib/recurs/rules.rb
|
52
|
+
- lib/recurs/version.rb
|
53
|
+
- lib/util/module.rb
|
54
|
+
- recurs.gemspec
|
55
|
+
- spec/models/event_spec.rb
|
56
|
+
- spec/models/recurrence_spec.rb
|
57
|
+
- spec/models/rules_spec.rb
|
58
|
+
- spec/spec_helper.rb
|
59
|
+
has_rdoc: true
|
60
|
+
homepage: http://rubygems.org/gems/recurs
|
61
|
+
licenses: []
|
62
|
+
|
63
|
+
post_install_message:
|
64
|
+
rdoc_options: []
|
65
|
+
|
66
|
+
require_paths:
|
67
|
+
- lib
|
68
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
69
|
+
none: false
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
segments:
|
74
|
+
- 0
|
75
|
+
version: "0"
|
76
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
77
|
+
none: false
|
78
|
+
requirements:
|
79
|
+
- - ">="
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
segments:
|
82
|
+
- 0
|
83
|
+
version: "0"
|
84
|
+
requirements: []
|
85
|
+
|
86
|
+
rubyforge_project: recurs
|
87
|
+
rubygems_version: 1.3.7
|
88
|
+
signing_key:
|
89
|
+
specification_version: 3
|
90
|
+
summary: A recurrence generator for ical format
|
91
|
+
test_files:
|
92
|
+
- spec/models/event_spec.rb
|
93
|
+
- spec/models/recurrence_spec.rb
|
94
|
+
- spec/models/rules_spec.rb
|
95
|
+
- spec/spec_helper.rb
|