clio-gcal4ruby 0.3.2
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.
- data/CHANGELOG +55 -0
- data/README.markdown +136 -0
- data/lib/gcal4ruby.rb +1 -0
- data/lib/gcal4ruby/base.rb +339 -0
- data/lib/gcal4ruby/calendar.rb +320 -0
- data/lib/gcal4ruby/event.rb +501 -0
- data/lib/gcal4ruby/recurrence.rb +213 -0
- data/lib/gcal4ruby/service.rb +100 -0
- data/test/unit.rb +180 -0
- metadata +78 -0
@@ -0,0 +1,213 @@
|
|
1
|
+
class Time
|
2
|
+
#Returns a ISO 8601 complete formatted string of the time
|
3
|
+
def complete
|
4
|
+
self.utc.strftime("%Y%m%dT%H%M%S")
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.parse_complete(value)
|
8
|
+
d, h = value.split("T")
|
9
|
+
return Time.parse(d+" "+h.gsub("Z", ""))
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
module GCal4Ruby
|
14
|
+
#The Recurrence class stores information on an Event's recurrence. The class implements
|
15
|
+
#the RFC 2445 iCalendar recurrence description.
|
16
|
+
class Recurrence
|
17
|
+
#The event start date/time
|
18
|
+
attr_reader :start
|
19
|
+
#The event end date/time
|
20
|
+
attr_reader :end
|
21
|
+
#the event reference
|
22
|
+
attr_reader :event
|
23
|
+
#The date until which the event will be repeated
|
24
|
+
attr_reader :repeat_until
|
25
|
+
#The event frequency
|
26
|
+
attr_reader :frequency
|
27
|
+
#True if the event is all day (i.e. no start/end time)
|
28
|
+
attr_accessor :all_day
|
29
|
+
|
30
|
+
#Accepts an optional attributes hash or a string containing a properly formatted ISO 8601 recurrence rule. Returns a new Recurrence object
|
31
|
+
def initialize(vars = {})
|
32
|
+
if vars.is_a? Hash
|
33
|
+
vars.each do |key, value|
|
34
|
+
self.send("#{key}=", value)
|
35
|
+
end
|
36
|
+
elsif vars.is_a? String
|
37
|
+
self.load(vars)
|
38
|
+
end
|
39
|
+
@all_day ||= false
|
40
|
+
end
|
41
|
+
|
42
|
+
#Accepts a string containing a properly formatted ISO 8601 recurrence rule and loads it into the recurrence object
|
43
|
+
def load(rec)
|
44
|
+
attrs = rec.split("\n")
|
45
|
+
attrs.each do |val|
|
46
|
+
key, value = val.split(":")
|
47
|
+
case key
|
48
|
+
when 'DTSTART'
|
49
|
+
@start = Time.parse_complete(value)
|
50
|
+
when 'DTSTART;VALUE=DATE'
|
51
|
+
@start = Time.parse(value)
|
52
|
+
@all_day = true
|
53
|
+
when 'DTSTART;VALUE=DATE-TIME'
|
54
|
+
@start = Time.parse_complete(value)
|
55
|
+
when 'DTEND'
|
56
|
+
@end = Time.parse_complete(value)
|
57
|
+
when 'DTEND;VALUE=DATE'
|
58
|
+
@end = Time.parse(value)
|
59
|
+
when 'DTEND;VALUE=DATE-TIME'
|
60
|
+
@end = Time.parse_complete(value)
|
61
|
+
when 'RRULE'
|
62
|
+
vals = value.split(";")
|
63
|
+
key = ''
|
64
|
+
by = ''
|
65
|
+
int = nil
|
66
|
+
vals.each do |rr|
|
67
|
+
a, h = rr.split("=")
|
68
|
+
case a
|
69
|
+
when 'FREQ'
|
70
|
+
key = h.downcase.capitalize
|
71
|
+
when 'INTERVAL'
|
72
|
+
int = h
|
73
|
+
when 'UNTIL'
|
74
|
+
@repeat_until = Time.parse(value)
|
75
|
+
else
|
76
|
+
by = h.split(",")
|
77
|
+
end
|
78
|
+
end
|
79
|
+
@frequency = {key => by}
|
80
|
+
@frequency.merge({'interval' => int}) if int
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
#Returns a string with the correctly formatted ISO 8601 recurrence rule
|
86
|
+
def to_s
|
87
|
+
|
88
|
+
output = ''
|
89
|
+
if @all_day
|
90
|
+
output += "DTSTART;VALUE=DATE:#{@start.utc.strftime("%Y%m%d")}\n"
|
91
|
+
else
|
92
|
+
output += "DTSTART;VALUE=DATE-TIME:#{@start.complete}\n"
|
93
|
+
end
|
94
|
+
if @all_day
|
95
|
+
output += "DTEND;VALUE=DATE:#{@end.utc.strftime("%Y%m%d")}\n"
|
96
|
+
else
|
97
|
+
output += "DTEND;VALUE=DATE-TIME:#{@end.complete}\n"
|
98
|
+
end
|
99
|
+
output += "RRULE:"
|
100
|
+
if @frequency
|
101
|
+
f = 'FREQ='
|
102
|
+
i = ''
|
103
|
+
by = ''
|
104
|
+
@frequency.each do |key, v|
|
105
|
+
if v.is_a?(Array)
|
106
|
+
if v.size > 0
|
107
|
+
value = v.join(",")
|
108
|
+
else
|
109
|
+
value = nil
|
110
|
+
end
|
111
|
+
else
|
112
|
+
value = v
|
113
|
+
end
|
114
|
+
f += "#{key.upcase};" if key != 'interval'
|
115
|
+
case key.downcase
|
116
|
+
when "secondly"
|
117
|
+
by += "BYSECOND=#{value};"
|
118
|
+
when "minutely"
|
119
|
+
by += "BYMINUTE=#{value};"
|
120
|
+
when "hourly"
|
121
|
+
by += "BYHOUR=#{value};"
|
122
|
+
when "weekly"
|
123
|
+
by += "BYDAY=#{value};" if value
|
124
|
+
when "monthly"
|
125
|
+
by += "BYDAY=#{value};"
|
126
|
+
when "yearly"
|
127
|
+
by += "BYYEARDAY=#{value};"
|
128
|
+
when 'interval'
|
129
|
+
i += "INTERVAL=#{value};"
|
130
|
+
end
|
131
|
+
end
|
132
|
+
output += f+i+by
|
133
|
+
end
|
134
|
+
if @repeat_until
|
135
|
+
output += "UNTIL=#{@repeat_until.strftime("%Y%m%d")}"
|
136
|
+
end
|
137
|
+
|
138
|
+
output += "\n"
|
139
|
+
end
|
140
|
+
|
141
|
+
#Sets the start date/time. Must be a Time object.
|
142
|
+
def start=(s)
|
143
|
+
if not s.is_a?(Time)
|
144
|
+
raise RecurrenceValueError, "Start must be a date or a time"
|
145
|
+
else
|
146
|
+
@start = s
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
#Sets the end Date/Time. Must be a Time object.
|
151
|
+
def end=(e)
|
152
|
+
if not e.is_a?(Time)
|
153
|
+
raise RecurrenceValueError, "End must be a date or a time"
|
154
|
+
else
|
155
|
+
@end = e
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
#Sets the parent event reference
|
160
|
+
def event=(e)
|
161
|
+
if not e.is_a?(Event)
|
162
|
+
raise RecurrenceValueError, "Event must be an event"
|
163
|
+
else
|
164
|
+
@event = e
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
#Sets the end date for the recurrence
|
169
|
+
def repeat_until=(r)
|
170
|
+
if not r.is_a?(Date)
|
171
|
+
raise RecurrenceValueError, "Repeat_until must be a date"
|
172
|
+
else
|
173
|
+
@repeat_until = r
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
#Sets the frequency of the recurrence. Should be a hash with one of
|
178
|
+
#"SECONDLY", "MINUTELY", "HOURLY", "DAILY", "WEEKLY", "MONTHLY", "YEARLY" as the key,
|
179
|
+
#and as the value, an array containing zero to n of the following:
|
180
|
+
#- *Secondly*: A value between 0 and 59. Causes the event to repeat on that second of each minut.
|
181
|
+
#- *Minutely*: A value between 0 and 59. Causes the event to repeat on that minute of every hour.
|
182
|
+
#- *Hourly*: A value between 0 and 23. Causes the even to repeat on that hour of every day.
|
183
|
+
#- *Daily*: No value needed - will cause the event to repeat every day until the repeat_until date.
|
184
|
+
#- *Weekly*: A value of the first two letters of a day of the week. Causes the event to repeat on that day.
|
185
|
+
#- *Monthly*: A value of a positive or negative integer (i.e. +1) prepended to a day-of-week string ('TU') to indicate the position of the day within the month. E.g. +1TU would be the first tuesday of the month.
|
186
|
+
#- *Yearly*: A value of 1 to 366 indicating the day of the year. May be negative to indicate counting down from the last day of the year.
|
187
|
+
#
|
188
|
+
#Optionally, you may specific a second hash pair to set the interval the event repeats:
|
189
|
+
# "interval" => '2'
|
190
|
+
#If the interval is missing, it is assumed to be 1.
|
191
|
+
#
|
192
|
+
#===Examples
|
193
|
+
#Repeat event every Tuesday:
|
194
|
+
# frequency = {"Weekly" => ["TU"]}
|
195
|
+
#
|
196
|
+
#Repeat every first and third Monday of the month
|
197
|
+
# frequency = {"Monthly" => ["+1MO", "+3MO"]}
|
198
|
+
#
|
199
|
+
#Repeat on the last day of every year
|
200
|
+
# frequency = {"Yearly" => [366]}
|
201
|
+
#
|
202
|
+
#Repeat every other week on Friday
|
203
|
+
# frequency = {"Weekly" => ["FR"], "interval" => "2"}
|
204
|
+
|
205
|
+
def frequency=(f)
|
206
|
+
if f.is_a?(Hash)
|
207
|
+
@frequency = f
|
208
|
+
else
|
209
|
+
raise RecurrenceValueError, "Frequency must be a hash (see documentation)"
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
require 'gcal4ruby/base'
|
2
|
+
require 'gcal4ruby/calendar'
|
3
|
+
|
4
|
+
module GCal4Ruby
|
5
|
+
|
6
|
+
#The service class is the main handler for all direct interactions with the
|
7
|
+
#Google Calendar API. A service represents a single user account. Each user
|
8
|
+
#account can have multiple calendars, so you'll need to find the calendar you
|
9
|
+
#want from the service, using the Calendar#find class method.
|
10
|
+
#=Usage
|
11
|
+
#
|
12
|
+
#1. Authenticate
|
13
|
+
# service = Service.new
|
14
|
+
# service.authenticate("user@gmail.com", "password")
|
15
|
+
#
|
16
|
+
#2. Get Calendar List
|
17
|
+
# calendars = service.calendars
|
18
|
+
#
|
19
|
+
|
20
|
+
class Service < Base
|
21
|
+
AUTH_TYPE_OAUTH = 'OAuth'
|
22
|
+
|
23
|
+
attr_accessor :account, :auth_token, :check_public, :auth_type
|
24
|
+
|
25
|
+
def initialize(attributes = {})
|
26
|
+
super()
|
27
|
+
attributes.each do |key, value|
|
28
|
+
self.send("#{key}=", value)
|
29
|
+
end
|
30
|
+
@check_public ||= true
|
31
|
+
end
|
32
|
+
|
33
|
+
def authenticate(username, password)
|
34
|
+
ret = nil
|
35
|
+
ret = send_post(AUTH_URL, "Email=#{username}&Passwd=#{password}&source=GCal4Ruby&service=cl&accountType=HOSTED_OR_GOOGLE")
|
36
|
+
if ret.class == Net::HTTPOK
|
37
|
+
@auth_token = ret.read_body.to_a[2].gsub("Auth=", "").strip
|
38
|
+
@account = username
|
39
|
+
@auth_type = 'ClientLogin'
|
40
|
+
return true
|
41
|
+
else
|
42
|
+
raise AuthenticationFailed
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# added authsub authentication. pass in the upgraded authsub token and the username/email address
|
47
|
+
def authsub_authenticate(authsub_token, account)
|
48
|
+
@auth_token = authsub_token
|
49
|
+
@account = account
|
50
|
+
@auth_type = 'AuthSub'
|
51
|
+
return true
|
52
|
+
end
|
53
|
+
|
54
|
+
def oauth_authenticate(access_token)
|
55
|
+
@auth_token = access_token
|
56
|
+
@auth_type = AUTH_TYPE_OAUTH
|
57
|
+
end
|
58
|
+
|
59
|
+
def oauth?
|
60
|
+
@auth_type == AUTH_TYPE_OAUTH
|
61
|
+
end
|
62
|
+
|
63
|
+
#Returns an array of Calendar objects for each calendar associated with
|
64
|
+
#the authenticated account.
|
65
|
+
def calendars
|
66
|
+
unless @auth_token
|
67
|
+
raise NotAuthenticated
|
68
|
+
end
|
69
|
+
ret = send_get(CALENDAR_LIST_FEED+"?max-results=100")
|
70
|
+
cals = []
|
71
|
+
REXML::Document.new(ret.body).root.elements.each("entry"){}.map do |entry|
|
72
|
+
entry.attributes["xmlns:gCal"] = "http://schemas.google.com/gCal/2005"
|
73
|
+
entry.attributes["xmlns:gd"] = "http://schemas.google.com/g/2005"
|
74
|
+
entry.attributes["xmlns:app"] = "http://www.w3.org/2007/app"
|
75
|
+
entry.attributes["xmlns"] = "http://www.w3.org/2005/Atom"
|
76
|
+
cal = Calendar.new(self)
|
77
|
+
cal.load("<?xml version='1.0' encoding='UTF-8'?>#{entry.to_s}")
|
78
|
+
cals << cal
|
79
|
+
end
|
80
|
+
return cals
|
81
|
+
end
|
82
|
+
|
83
|
+
# This is for building a composite calendar
|
84
|
+
# I'm sure it doesn't work, needs review!
|
85
|
+
def to_iframe(cals, params = {})
|
86
|
+
calendar_set = case cals
|
87
|
+
when :all then calendars
|
88
|
+
when :first then calendars[0]
|
89
|
+
else cals
|
90
|
+
end
|
91
|
+
|
92
|
+
units = calendar_set.collect do |cal|
|
93
|
+
"src=#{cal.id}" + cal.build_options_set(params).join("&")
|
94
|
+
end
|
95
|
+
|
96
|
+
"<iframe src='http://www.google.com/calendar/embed?#{units.join("&")}' width='#{params[:width]}' height='#{params[:height]}' frameborder='#{params[:border]}' scrolling='no'></iframe>"
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
data/test/unit.rb
ADDED
@@ -0,0 +1,180 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'gcal4ruby'
|
5
|
+
include GCal4Ruby
|
6
|
+
|
7
|
+
@service = Service.new
|
8
|
+
@username = nil
|
9
|
+
@password = nil
|
10
|
+
|
11
|
+
def tester
|
12
|
+
if ARGV.include?("-d")
|
13
|
+
@service.debug = true
|
14
|
+
end
|
15
|
+
ARGV.each do |ar|
|
16
|
+
if ar.match("username=")
|
17
|
+
@username = ar.gsub("username=", "")
|
18
|
+
end
|
19
|
+
if ar.match("password=")
|
20
|
+
@password = ar.gsub("password=", "")
|
21
|
+
end
|
22
|
+
end
|
23
|
+
service_test
|
24
|
+
calendar_test
|
25
|
+
event_test
|
26
|
+
event_recurrence_test
|
27
|
+
end
|
28
|
+
|
29
|
+
def service_test
|
30
|
+
puts "---Starting Service Test---"
|
31
|
+
puts "1. Authenticate"
|
32
|
+
if @service.authenticate(@username, @password)
|
33
|
+
successful
|
34
|
+
else
|
35
|
+
failed
|
36
|
+
end
|
37
|
+
|
38
|
+
puts "2. Calendar List"
|
39
|
+
cals = @service.calendars
|
40
|
+
if cals
|
41
|
+
successful "Calendars for this Account:"
|
42
|
+
cals.each do |cal|
|
43
|
+
puts cal.title
|
44
|
+
end
|
45
|
+
else
|
46
|
+
failed
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def calendar_test
|
51
|
+
puts "---Starting Calendar Test---"
|
52
|
+
|
53
|
+
puts "1. Create Calendar"
|
54
|
+
cal = Calendar.new(@service)
|
55
|
+
cal.title = "test calendar"+Time.now.to_s
|
56
|
+
puts "Calender exists = "+cal.exists?.to_s
|
57
|
+
if cal.save
|
58
|
+
successful cal.to_xml
|
59
|
+
else
|
60
|
+
failed
|
61
|
+
end
|
62
|
+
|
63
|
+
puts "2. Edit Calendar"
|
64
|
+
cal.title = "renamed title"
|
65
|
+
if cal.save
|
66
|
+
successful cal.to_xml
|
67
|
+
else
|
68
|
+
puts "Test 2 Failed"
|
69
|
+
end
|
70
|
+
|
71
|
+
puts "3. Find Calendar by ID"
|
72
|
+
c = Calendar.find(@service, cal.id)
|
73
|
+
if c.title == cal.title
|
74
|
+
successful
|
75
|
+
else
|
76
|
+
failed "#{c.title} not equal to #{cal.title}"
|
77
|
+
end
|
78
|
+
|
79
|
+
puts "4. Delete Calendar"
|
80
|
+
if cal.delete and not cal.title
|
81
|
+
successful
|
82
|
+
else
|
83
|
+
failed
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def event_test
|
88
|
+
puts "---Starting Event Test---"
|
89
|
+
|
90
|
+
puts "1. Create Event"
|
91
|
+
event = Event.new(@service.calendars[0])
|
92
|
+
event.title = "Test Event"
|
93
|
+
event.content = "Test event content"
|
94
|
+
event.start = Time.now+1800
|
95
|
+
event.end = Time.now+5400
|
96
|
+
if event.save
|
97
|
+
successful event.to_xml
|
98
|
+
else
|
99
|
+
failed
|
100
|
+
end
|
101
|
+
|
102
|
+
puts "2. Edit Event"
|
103
|
+
event.title = "Edited title"
|
104
|
+
if event.save
|
105
|
+
successful event.to_xml
|
106
|
+
else
|
107
|
+
failed
|
108
|
+
end
|
109
|
+
|
110
|
+
puts "3. Reload Event"
|
111
|
+
if event.reload
|
112
|
+
successful
|
113
|
+
end
|
114
|
+
|
115
|
+
puts "4. Find Event by id"
|
116
|
+
e = Event.find(@service.calendars[0], event.id)
|
117
|
+
if e.title == event.title
|
118
|
+
successful
|
119
|
+
else
|
120
|
+
failed "Found event doesn't match existing event"
|
121
|
+
end
|
122
|
+
|
123
|
+
puts "5. Delete Event"
|
124
|
+
if event.delete
|
125
|
+
successful
|
126
|
+
else
|
127
|
+
failed
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def event_recurrence_test
|
132
|
+
puts "---Starting Event Recurrence Test---"
|
133
|
+
|
134
|
+
@first_start = Time.now
|
135
|
+
@first_end = Time.now+3600
|
136
|
+
@first_freq = {'weekly' => ['TU']}
|
137
|
+
@second_start = Time.now+86000
|
138
|
+
@second_end = Time.now+89600
|
139
|
+
@second_freq = {'weekly' => ['SA']}
|
140
|
+
|
141
|
+
puts "1. Create Recurring Event"
|
142
|
+
event = Event.new(@service.calendars[0])
|
143
|
+
event.title = "Test Recurring Event"
|
144
|
+
event.content = "Test event content"
|
145
|
+
event.recurrence = Recurrence.new({:start => @first_start, :end => @first_end, :frequency => @first_freq})
|
146
|
+
if event.save
|
147
|
+
successful event.to_xml
|
148
|
+
else
|
149
|
+
failed("recurrence = "+event.recurrence.to_s)
|
150
|
+
end
|
151
|
+
|
152
|
+
puts "2. Edit Recurrence"
|
153
|
+
event.title = "Edited recurring title"
|
154
|
+
event.recurrence = Recurrence.new({:start => @second_start, :end => @second_end, :frequency => @second_freq})
|
155
|
+
if event.save
|
156
|
+
successful event.to_xml
|
157
|
+
else
|
158
|
+
failed
|
159
|
+
end
|
160
|
+
|
161
|
+
puts "3. Delete Event"
|
162
|
+
if event.delete
|
163
|
+
successful
|
164
|
+
else
|
165
|
+
failed
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
def failed(m = nil)
|
170
|
+
puts "Test Failed"
|
171
|
+
puts m if m
|
172
|
+
exit()
|
173
|
+
end
|
174
|
+
|
175
|
+
def successful(m = nil)
|
176
|
+
puts "Test Successful"
|
177
|
+
puts m if m
|
178
|
+
end
|
179
|
+
|
180
|
+
tester
|