vpim 0.16 → 0.17
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/lib/vpim/agent/plist.rb +86 -0
- data/lib/vpim/date.rb +1 -3
- data/lib/vpim/date.rb~ +198 -0
- data/lib/vpim/dirinfo.rb +47 -15
- data/lib/vpim/dirinfo.rb~ +242 -0
- data/lib/vpim/duration.rb +1 -3
- data/lib/vpim/duration.rb~ +121 -0
- data/lib/vpim/enumerator.rb +1 -3
- data/lib/vpim/enumerator.rb~ +29 -0
- data/lib/vpim/field.rb +141 -56
- data/lib/vpim/field.rb~ +594 -0
- data/lib/vpim/icalendar.rb +10 -16
- data/lib/vpim/icalendar.rb~ +548 -0
- data/lib/vpim/maker/vcard.rb +124 -46
- data/lib/vpim/maker/vcard.rb~ +382 -0
- data/lib/vpim/rfc2425.rb +30 -17
- data/lib/vpim/rfc2425.rb~ +246 -0
- data/lib/vpim/rrule.rb +2 -4
- data/lib/vpim/rrule.rb~ +482 -0
- data/lib/vpim/time.rb +1 -3
- data/lib/vpim/time.rb~ +42 -0
- data/lib/vpim/vcard.rb +84 -18
- data/lib/vpim/vcard.rb~ +232 -0
- data/lib/vpim/vevent.rb +1 -3
- data/lib/vpim/vevent.rb~ +381 -0
- data/lib/vpim/vpim.rb +61 -29
- data/lib/vpim/vpim.rb~ +61 -29
- metadata +16 -2
data/lib/vpim/icalendar.rb
CHANGED
@@ -1,7 +1,5 @@
|
|
1
1
|
=begin
|
2
|
-
|
3
|
-
|
4
|
-
Copyright (C) 2005 Sam Roberts
|
2
|
+
Copyright (C) 2006 Sam Roberts
|
5
3
|
|
6
4
|
This library is free software; you can redistribute it and/or modify it
|
7
5
|
under the same terms as the ruby language itself, see the file COPYING for
|
@@ -264,7 +262,7 @@ module Vpim
|
|
264
262
|
|
265
263
|
# Check if the protocol method is +method+
|
266
264
|
def protocol?(method)
|
267
|
-
protocol
|
265
|
+
Vpim::Methods.casecmp?(protocol, method)
|
268
266
|
end
|
269
267
|
|
270
268
|
def Icalendar.decode_duration(str) #:nodoc:
|
@@ -313,17 +311,10 @@ module Vpim
|
|
313
311
|
# END:VCALENDAR), multiple iCalendars can be concatenated into a single
|
314
312
|
# file.
|
315
313
|
#
|
316
|
-
# cal must be
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
elsif cal.kind_of? IO
|
321
|
-
string = cal.read(nil)
|
322
|
-
else
|
323
|
-
raise ArgumentError, "Icalendar.decode cannot be called with a #{cal.type}"
|
324
|
-
end
|
325
|
-
|
326
|
-
entities = Vpim.expand(Vpim.decode(string))
|
314
|
+
# cal must be String or IO, or implement #each by returning
|
315
|
+
# each line in the input as those classes do.
|
316
|
+
def Icalendar.decode(cal, e = nil)
|
317
|
+
entities = Vpim.expand(Vpim.decode(cal))
|
327
318
|
|
328
319
|
# Since all iCalendars must have a begin/end, the top-level should
|
329
320
|
# consist entirely of entities/arrays, even if its a single iCalendar.
|
@@ -465,8 +456,11 @@ module Vpim
|
|
465
456
|
|
466
457
|
# Return true if the +uri+ is == to this address' URI. The comparison
|
467
458
|
# is case-insensitive.
|
459
|
+
#
|
460
|
+
# FIXME - why case insensitive? Email addresses. Should use a URI library
|
461
|
+
# if I can find one and it knows how to do URI comparisons.
|
468
462
|
def ==(uri)
|
469
|
-
self.uri.
|
463
|
+
Vpim::Methods.casecmp?(self.uri.to_str, uri.to_str)
|
470
464
|
end
|
471
465
|
|
472
466
|
# The common or displayable name associated with the calendar address,
|
@@ -0,0 +1,548 @@
|
|
1
|
+
=begin
|
2
|
+
$Id: icalendar.rb,v 1.24 2005/01/07 03:32:44 sam Exp $
|
3
|
+
|
4
|
+
Copyright (C) 2005 Sam Roberts
|
5
|
+
|
6
|
+
This library is free software; you can redistribute it and/or modify it
|
7
|
+
under the same terms as the ruby language itself, see the file COPYING for
|
8
|
+
details.
|
9
|
+
=end
|
10
|
+
|
11
|
+
require 'vpim/rfc2425'
|
12
|
+
require 'vpim/dirinfo'
|
13
|
+
require 'vpim/rrule'
|
14
|
+
require 'vpim/vevent'
|
15
|
+
require 'vpim/vpim'
|
16
|
+
|
17
|
+
=begin
|
18
|
+
|
19
|
+
... ; y/n (whether I've seen it in Apple's calendars)
|
20
|
+
name
|
21
|
+
section
|
22
|
+
type/comments
|
23
|
+
|
24
|
+
|
25
|
+
icalbody = icalprops component
|
26
|
+
|
27
|
+
icalprops =
|
28
|
+
prodid / ; y PRODID 4.7.3 required, TEXT
|
29
|
+
version / ; y 4.7.4 required, TEXT, "2.0"
|
30
|
+
calscal / ; y 4.7.1 only defined value is GREGORIAN
|
31
|
+
method / ; n METHOD 4.7.2 used with transport protocols
|
32
|
+
|
33
|
+
component =
|
34
|
+
eventc / ; y VEVENT 4.6.1
|
35
|
+
todoc / ; y VTODO 4.6.2
|
36
|
+
journalc / ; n
|
37
|
+
freebusyc / ; n
|
38
|
+
timezonec / ; n
|
39
|
+
|
40
|
+
alarmc ; y VALARM 4.6.6 occurs inside a VEVENT or a VTODO
|
41
|
+
|
42
|
+
class ; y CLASS 4.8.1.3 private/public/confidentical/... (default=public)
|
43
|
+
|
44
|
+
comment ; n 4.8.1.4 TEXT
|
45
|
+
description ; y 4.8.1.5 TEXT
|
46
|
+
summary ; y 4.8.1.12 TEXT
|
47
|
+
location ; y 4.8.1.7 TEXT intended venue
|
48
|
+
|
49
|
+
priority ; n why? 4.8.1.9 INTEGER, why isn't this seen for my TODO items?
|
50
|
+
|
51
|
+
status ; y 4.8.1.11 TEXT, different values defined for event, todo, journal
|
52
|
+
|
53
|
+
Event: TENTATIVE, CONFIRMED, CANCELLED
|
54
|
+
|
55
|
+
Todo: NEEDS-ACTION, COMPLETED, IN-PROCESS, CANCELLED
|
56
|
+
|
57
|
+
Journal: DRAFT, FINAL, CANCELLED
|
58
|
+
|
59
|
+
dtstart ; y DTSTART 4.8.2.4 DATE-TIME is default, value=date can be set
|
60
|
+
dtend ; y DTEND 4.8.2.2 Unless it has Z (UTC), or a tzid, then it is local-time.
|
61
|
+
|
62
|
+
dtstamp ; y DTSTAMP 4.8.7.2 DATE-TIME, creation time, inclusion is mandatory, but what does
|
63
|
+
it mean? It seems to be when the icalendar was actually created (as opposed to when the user entered
|
64
|
+
the information into the calendar database, for example), but in that case my Apple icalendars should
|
65
|
+
have all components having the same DTSTAMP, but they don't!
|
66
|
+
|
67
|
+
duration ; y DURATION 4.8.2.5 dur-value
|
68
|
+
|
69
|
+
dur-value = (["+"] / "-") "P" (dur-date / dur-time / dur-week)
|
70
|
+
|
71
|
+
dur-date = dur-day [dur-time]
|
72
|
+
= 1*DIGIT "D" [ dur-time ]
|
73
|
+
|
74
|
+
dur-time = "T" (dur-hour / dur-minute / dur-second)
|
75
|
+
= "T" (
|
76
|
+
1*DIGIT "H" [ 1*DIGIT "M" [ 1*DIGIT "S" ] ] /
|
77
|
+
1*DIGIT "M" [ 1*DIGIT "S" ] /
|
78
|
+
1*DIGIT "S"
|
79
|
+
)
|
80
|
+
|
81
|
+
dur-week = 1*DIGIT "W"
|
82
|
+
dur-day = 1*DIGIT "D"
|
83
|
+
dur-hour = 1*DIGIT "H" [dur-minute]
|
84
|
+
dur-minute = 1*DIGIT "M" [dur-second]
|
85
|
+
dur-second = 1*DIGIT "S"
|
86
|
+
|
87
|
+
The EBNF is complicated, because they want to say that /some/ component
|
88
|
+
must be present, and that if you have a "T", you need a time after it,
|
89
|
+
and that you can't have an hour followed by seconds with no intervening
|
90
|
+
minutes... but we don't care about that during decoding, so we rewrite
|
91
|
+
the EBNF as:
|
92
|
+
|
93
|
+
dur-value = ["+" / "-"] "P" [ 1*DIGIT "W" ] [ 1*DIGIT "D" ] [ "T" [ 1*DIGIT "H" ] [ 1*DIGIT "M" ] [ 1*DIGIT "S" ] ]
|
94
|
+
|
95
|
+
dtdue ; n DTDUE 4.8.2.3
|
96
|
+
|
97
|
+
uid ; y UID 4.8.4.7 TEXT, recommended to generate them in RFC822 form
|
98
|
+
|
99
|
+
rrule ; y RRULE 4.8.5.4 RECUR, can occur multiple times!
|
100
|
+
|
101
|
+
|
102
|
+
|
103
|
+
VEVENT Ifx:
|
104
|
+
|
105
|
+
TEXT: summary, description, comment, location, uid
|
106
|
+
|
107
|
+
think about: uid
|
108
|
+
|
109
|
+
Vevent#status -> The status, upper-case.
|
110
|
+
Vevent#status= -> set a new status, only allow the defined statuses!
|
111
|
+
Vevent#status?(s) -> check if the status is s
|
112
|
+
|
113
|
+
can contain alarms... should it include an alarms module?
|
114
|
+
|
115
|
+
=end
|
116
|
+
|
117
|
+
module Vpim
|
118
|
+
# An iCalendar.
|
119
|
+
#
|
120
|
+
# A Calendar is some meta-information followed by a sequence of components.
|
121
|
+
#
|
122
|
+
# Defined components are Event, Todo, Freebusy, Journal, and Timezone, each
|
123
|
+
# of which are represented by their own class, though they share many
|
124
|
+
# properties in common. For example, Event and Todo may both contain
|
125
|
+
# multiple Alarm components.
|
126
|
+
#
|
127
|
+
# = Reference
|
128
|
+
#
|
129
|
+
# The iCalendar format is specified by a series of IETF documents:
|
130
|
+
#
|
131
|
+
# - link:rfc2445.txt: Internet Calendaring and Scheduling Core Object Specification
|
132
|
+
# - link:rfc2446.txt: iCalendar Transport-Independent Interoperability Protocol
|
133
|
+
# (iTIP) Scheduling Events, BusyTime, To-dos and Journal Entries
|
134
|
+
# - link:rfc2447.txt: iCalendar Message-Based Interoperability Protocol
|
135
|
+
#
|
136
|
+
# iCalendar (RFC 2445) is based on vCalendar, but does not appear to be
|
137
|
+
# altogether compatible. iCalendar files have VERSION:2.0 and vCalendar have
|
138
|
+
# VERSION:1.0. While much appears to be similar, the recurrence rule syntax,
|
139
|
+
# at least, is completely different.
|
140
|
+
#
|
141
|
+
# iCalendars are usually transmitted in files with <code>.ics</code>
|
142
|
+
# extensions.
|
143
|
+
class Icalendar
|
144
|
+
include Vpim
|
145
|
+
|
146
|
+
# Regular expression strings for the EBNF of RFC 2445
|
147
|
+
module Bnf #:nodoc:
|
148
|
+
# dur-value = ["+" / "-"] "P" [ 1*DIGIT "W" ] [ 1*DIGIT "D" ] [ "T" [ 1*DIGIT "H" ] [ 1*DIGIT "M" ] [ 1*DIGIT "S" ] ]
|
149
|
+
DURATION = '([-+])?P(\d+W)?(\d+D)?T?(\d+H)?(\d+M)?(\d+S)?'
|
150
|
+
end
|
151
|
+
|
152
|
+
private_class_method :new
|
153
|
+
|
154
|
+
# Create a new Icalendar object from +fields+, an array of
|
155
|
+
# DirectoryInfo::Field objects.
|
156
|
+
#
|
157
|
+
# When decoding Calendar data, you would usually use Icalendar.decode(),
|
158
|
+
# which decodes the data into the field arrays, and calls this method
|
159
|
+
# for each Calendar it finds.
|
160
|
+
def initialize(fields) #:nodoc:
|
161
|
+
# seperate into the outer-level fields, and the arrays of component
|
162
|
+
# fields
|
163
|
+
outer, inner = Vpim.outer_inner(fields)
|
164
|
+
|
165
|
+
# Make a dirinfo out of outer, and check its an iCalendar
|
166
|
+
@properties = DirectoryInfo.create(outer)
|
167
|
+
@properties.check_begin_end('VCALENDAR')
|
168
|
+
|
169
|
+
# Categorize the components
|
170
|
+
@vevents = []
|
171
|
+
@vtodos = []
|
172
|
+
@others = []
|
173
|
+
|
174
|
+
inner.each do |component|
|
175
|
+
# First field in every component should be a "BEGIN:".
|
176
|
+
name = component.first
|
177
|
+
if ! name.name? 'begin'
|
178
|
+
raise InvalidEncodingError, "calendar component begins with #{name.name}, instead of BEGIN!"
|
179
|
+
end
|
180
|
+
|
181
|
+
name = name.value.upcase
|
182
|
+
|
183
|
+
case name
|
184
|
+
when 'VEVENT' then @vevents << Vevent.new(component)
|
185
|
+
when 'VTODO' then @vtodos << Vtodo.new(component)
|
186
|
+
else @others << component
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
# Create a new Icalendar object with the minimal set of fields for a valid
|
192
|
+
# Calendar. If specified, +fields+ must be an array of
|
193
|
+
# DirectoryInfo::Field objects to add. They can override the the default
|
194
|
+
# Calendar fields, so, for example, this can be used to set a custom PRODID field.
|
195
|
+
#
|
196
|
+
# TODO - allow hash args like Vevent.create
|
197
|
+
def Icalendar.create(fields=[])
|
198
|
+
di = DirectoryInfo.create( [ DirectoryInfo::Field.create('VERSION', '2.0') ], 'VCALENDAR' )
|
199
|
+
|
200
|
+
DirectoryInfo::Field.create_array(fields).each { |f| di.push_unique f }
|
201
|
+
|
202
|
+
di.push_unique DirectoryInfo::Field.create('PRODID', "-//Ensemble Independant//vPim #{Vpim.version}//EN")
|
203
|
+
di.push_unique DirectoryInfo::Field.create('CALSCALE', "Gregorian")
|
204
|
+
|
205
|
+
new(di.to_a)
|
206
|
+
end
|
207
|
+
|
208
|
+
# Create a new Icalendar object with a protocol method of REPLY.
|
209
|
+
#
|
210
|
+
# Meeting requests, and such, are Calendar containers with a protocol
|
211
|
+
# method of REQUEST, and contains some number of Events, Todos, etc.,
|
212
|
+
# that may need replying to. In order to reply to any of these components
|
213
|
+
# of a request, you must first build a Calendar object to hold your reply
|
214
|
+
# components.
|
215
|
+
#
|
216
|
+
# This method builds the reply Calendar, you then will add to it replies
|
217
|
+
# to the specific components of the request Calendar that you are replying
|
218
|
+
# to. If you have any particular fields that you want to be in the
|
219
|
+
# Calendar, other than the defaults, then can be supplied as +fields+, an
|
220
|
+
# array of Field objects.
|
221
|
+
def Icalendar.create_reply(fields=[])
|
222
|
+
fields << DirectoryInfo::Field.create('METHOD', 'REPLY')
|
223
|
+
|
224
|
+
Icalendar.create(fields)
|
225
|
+
end
|
226
|
+
|
227
|
+
# Encode the Calendar as a string. The width is the maximum width of the
|
228
|
+
# encoded lines, it can be specified, but is better left to the default.
|
229
|
+
#
|
230
|
+
# TODO - only does top-level now, needs to add the events/todos/etc.
|
231
|
+
def encode(width=nil)
|
232
|
+
# We concatenate the fields of all objects, create a DirInfo, then
|
233
|
+
# encode it.
|
234
|
+
di = DirectoryInfo.create(self.fields.flatten)
|
235
|
+
di.encode(width)
|
236
|
+
end
|
237
|
+
|
238
|
+
# Used during encoding.
|
239
|
+
def fields # :nodoc:
|
240
|
+
fields = @properties.to_a
|
241
|
+
|
242
|
+
last = fields.pop
|
243
|
+
|
244
|
+
@vevents.each { |c| fields << c.fields }
|
245
|
+
@vtodos.each { |c| fields << c.fields }
|
246
|
+
@others.each { |c| fields << c.fields }
|
247
|
+
|
248
|
+
fields << last
|
249
|
+
end
|
250
|
+
|
251
|
+
alias to_s encode
|
252
|
+
|
253
|
+
# Push a calendar component onto the calendar.
|
254
|
+
def push(component)
|
255
|
+
case component
|
256
|
+
when Vevent
|
257
|
+
@vevents << component
|
258
|
+
when Vtodo
|
259
|
+
@vtodos << component
|
260
|
+
else
|
261
|
+
raise ArgumentError, "can't add component type #{component.type} to a calendar"
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
# Check if the protocol method is +method+
|
266
|
+
def protocol?(method)
|
267
|
+
protocol == method.upcase
|
268
|
+
end
|
269
|
+
|
270
|
+
def Icalendar.decode_duration(str) #:nodoc:
|
271
|
+
unless match = %r{\s*#{Bnf::DURATION}\s*}.match(str)
|
272
|
+
raise InvalidEncodingError, "duration not valid (#{str})"
|
273
|
+
end
|
274
|
+
dur = 0
|
275
|
+
|
276
|
+
# Remember: match[0] is the whole match string, match[1] is $1, etc.
|
277
|
+
|
278
|
+
# Week
|
279
|
+
if match[2]
|
280
|
+
dur = match[2].to_i
|
281
|
+
end
|
282
|
+
# Days
|
283
|
+
dur *= 7
|
284
|
+
if match[3]
|
285
|
+
dur += match[3].to_i
|
286
|
+
end
|
287
|
+
# Hours
|
288
|
+
dur *= 24
|
289
|
+
if match[4]
|
290
|
+
dur += match[4].to_i
|
291
|
+
end
|
292
|
+
# Minutes
|
293
|
+
dur *= 60
|
294
|
+
if match[5]
|
295
|
+
dur += match[5].to_i
|
296
|
+
end
|
297
|
+
# Seconds
|
298
|
+
dur *= 60
|
299
|
+
if match[6]
|
300
|
+
dur += match[6].to_i
|
301
|
+
end
|
302
|
+
|
303
|
+
if match[1] && match[1] == '-'
|
304
|
+
dur = -dur
|
305
|
+
end
|
306
|
+
|
307
|
+
dur
|
308
|
+
end
|
309
|
+
|
310
|
+
# Decode iCalendar data into an array of Icalendar objects.
|
311
|
+
#
|
312
|
+
# Since iCalendars are self-delimited (by a BEGIN:VCALENDAR and an
|
313
|
+
# END:VCALENDAR), multiple iCalendars can be concatenated into a single
|
314
|
+
# file.
|
315
|
+
#
|
316
|
+
# cal must be String or IO, or implement #each by returning
|
317
|
+
# each line in the input as those classes do.
|
318
|
+
def Icalendar.decode(cal, e = nil)
|
319
|
+
entities = Vpim.expand(Vpim.decode(cal))
|
320
|
+
|
321
|
+
# Since all iCalendars must have a begin/end, the top-level should
|
322
|
+
# consist entirely of entities/arrays, even if its a single iCalendar.
|
323
|
+
if entities.detect { |e| ! e.kind_of? Array }
|
324
|
+
raise "Not a valid iCalendar"
|
325
|
+
end
|
326
|
+
|
327
|
+
calendars = []
|
328
|
+
|
329
|
+
entities.each do |e|
|
330
|
+
calendars << new(e)
|
331
|
+
end
|
332
|
+
|
333
|
+
calendars
|
334
|
+
end
|
335
|
+
|
336
|
+
# The iCalendar version multiplied by 10 as an Integer. If no VERSION field
|
337
|
+
# is present (which is non-conformant), nil is returned. iCalendar must
|
338
|
+
# have a version of 20, and vCalendar would have a version of 10.
|
339
|
+
def version
|
340
|
+
v = @properties['VERSION']
|
341
|
+
|
342
|
+
unless v
|
343
|
+
raise InvalidEncodingError, "Invalid calendar, no version field!"
|
344
|
+
end
|
345
|
+
|
346
|
+
v = v.to_f * 10
|
347
|
+
v = v.to_i
|
348
|
+
end
|
349
|
+
|
350
|
+
# The value of the PRODID field, an unstructured string meant to
|
351
|
+
# identify the software which encoded the Calendar data.
|
352
|
+
def producer
|
353
|
+
#f = @properties.field('PRODID')
|
354
|
+
#f && f.to_text
|
355
|
+
@properties.text('PRODID').first
|
356
|
+
end
|
357
|
+
|
358
|
+
# The value of the METHOD field. Protocol methods are used when iCalendars
|
359
|
+
# are exchanged in a calendar messaging system, such as iTIP or iMIP. When
|
360
|
+
# METHOD is not specified, the Calendar object is merely being used to
|
361
|
+
# transport a snapshot of some calendar information; without the intention
|
362
|
+
# of conveying a scheduling semantic.
|
363
|
+
#
|
364
|
+
# Note that this can't be called 'method', that name is reserved.
|
365
|
+
def protocol
|
366
|
+
m = @properties['METHOD']
|
367
|
+
m ? m.upcase : m
|
368
|
+
end
|
369
|
+
|
370
|
+
# The array of all calendar events (each is a Vevent).
|
371
|
+
#
|
372
|
+
# TODO - should this take an interval: t0,t1?
|
373
|
+
def events
|
374
|
+
@vevents
|
375
|
+
end
|
376
|
+
|
377
|
+
# The array of all calendar todos (each is a Vtodo).
|
378
|
+
def todos
|
379
|
+
@vtodos
|
380
|
+
end
|
381
|
+
end
|
382
|
+
|
383
|
+
end
|
384
|
+
|
385
|
+
=begin
|
386
|
+
|
387
|
+
Notes on a CAL-ADDRESS
|
388
|
+
|
389
|
+
When used with ATTENDEE, the parameters are:
|
390
|
+
CN
|
391
|
+
CUTYPE
|
392
|
+
DELEGATED-FROM
|
393
|
+
DELEGATED-TO
|
394
|
+
DIR
|
395
|
+
LANGUAGE
|
396
|
+
MEMBER
|
397
|
+
PARTSTAT
|
398
|
+
ROLE
|
399
|
+
RSVP
|
400
|
+
SENT-BY
|
401
|
+
|
402
|
+
When used with ORGANIZER, the parameters are:
|
403
|
+
CN
|
404
|
+
DIR
|
405
|
+
LANGUAGE
|
406
|
+
SENT-BY
|
407
|
+
|
408
|
+
|
409
|
+
What I've seen in Notes invitations, and iCal responses:
|
410
|
+
ROLE
|
411
|
+
PARTSTAT
|
412
|
+
RSVP
|
413
|
+
CN
|
414
|
+
|
415
|
+
Support these last 4, for now.
|
416
|
+
|
417
|
+
=end
|
418
|
+
|
419
|
+
module Vpim
|
420
|
+
class Icalendar
|
421
|
+
# Used to represent calendar fields containing CAL-ADDRESS values.
|
422
|
+
# The organizer or the attendees of a calendar event are examples of such
|
423
|
+
# a field.
|
424
|
+
#
|
425
|
+
# Example:
|
426
|
+
# ORGANIZER;CN="A. Person":mailto:a_person@example.com
|
427
|
+
# ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION
|
428
|
+
# ;CN="Sam Roberts";RSVP=TRUE:mailto:SRoberts@example.com
|
429
|
+
#
|
430
|
+
class Address
|
431
|
+
|
432
|
+
# Create an Address from a DirectoryInfo::Field, +field+.
|
433
|
+
#
|
434
|
+
# TODO - make private, and split into the encode/decode/create trinity.
|
435
|
+
def initialize(field)
|
436
|
+
unless field.value
|
437
|
+
raise ArgumentError
|
438
|
+
end
|
439
|
+
|
440
|
+
@field = field
|
441
|
+
end
|
442
|
+
|
443
|
+
# Return a representation of this Address as a DirectoryInfo::Field.
|
444
|
+
def field
|
445
|
+
@field.copy
|
446
|
+
end
|
447
|
+
|
448
|
+
# Create a copy of Address. If the original Address was frozen, this one
|
449
|
+
# won't be.
|
450
|
+
def copy
|
451
|
+
Marshal.load(Marshal.dump(self))
|
452
|
+
end
|
453
|
+
|
454
|
+
# Addresses in a CAL-ADDRESS are represented as a URI, usually a mailto URI.
|
455
|
+
def uri
|
456
|
+
@field.value
|
457
|
+
end
|
458
|
+
|
459
|
+
# Return true if the +uri+ is == to this address' URI. The comparison
|
460
|
+
# is case-insensitive.
|
461
|
+
def ==(uri)
|
462
|
+
self.uri.downcase == uri.downcase
|
463
|
+
end
|
464
|
+
|
465
|
+
# The common or displayable name associated with the calendar address,
|
466
|
+
# or nil if there is none.
|
467
|
+
def cn
|
468
|
+
return nil unless n = @field.param('CN')
|
469
|
+
|
470
|
+
# FIXME = the CN param may have no value, which is an error, but don't try
|
471
|
+
# to decode it, return either nil, or InvalidEncoding
|
472
|
+
Vpim.decode_text(n.first)
|
473
|
+
end
|
474
|
+
|
475
|
+
# A string representation of an address, using the common name, and the
|
476
|
+
# URI. The URI protocol is stripped if it's "mailto:".
|
477
|
+
#
|
478
|
+
# TODO - this needs to properly escape the cn string!
|
479
|
+
def to_s
|
480
|
+
u = uri
|
481
|
+
u.gsub!(/^mailto: */i, '')
|
482
|
+
|
483
|
+
if cn
|
484
|
+
"\"#{cn}\" <#{uri}>"
|
485
|
+
else
|
486
|
+
uri
|
487
|
+
end
|
488
|
+
end
|
489
|
+
|
490
|
+
# The participation role for the calendar user specified by the address.
|
491
|
+
#
|
492
|
+
# The standard roles are:
|
493
|
+
# - CHAIR Indicates chair of the calendar entity
|
494
|
+
# - REQ-PARTICIPANT Indicates a participant whose participation is required
|
495
|
+
# - OPT-PARTICIPANT Indicates a participant whose participation is optional
|
496
|
+
# - NON-PARTICIPANT Indicates a participant who is copied for information purposes only
|
497
|
+
#
|
498
|
+
# The default role is REQ-PARTICIPANT, returned if no ROLE parameter was
|
499
|
+
# specified.
|
500
|
+
def role
|
501
|
+
return 'REQ-PARTICIPANT' unless r = @field.param('ROLE')
|
502
|
+
r.first.upcase
|
503
|
+
end
|
504
|
+
|
505
|
+
# The participation status for the calendar user specified by the
|
506
|
+
# property PARTSTAT, a String.
|
507
|
+
#
|
508
|
+
# These are the participation statuses for an Event:
|
509
|
+
# - NEEDS-ACTION Event needs action
|
510
|
+
# - ACCEPTED Event accepted
|
511
|
+
# - DECLINED Event declined
|
512
|
+
# - TENTATIVE Event tentatively accepted
|
513
|
+
# - DELEGATED Event delegated
|
514
|
+
#
|
515
|
+
# Default is NEEDS-ACTION.
|
516
|
+
#
|
517
|
+
# TODO - make the default depend on the component type.
|
518
|
+
def partstat
|
519
|
+
return 'NEEDS-ACTION' unless r = @field.param('PARTSTAT')
|
520
|
+
r.first.upcase
|
521
|
+
end
|
522
|
+
|
523
|
+
# Set or change the participation status of the address, the PARTSTAT,
|
524
|
+
# to +status+.
|
525
|
+
#
|
526
|
+
# See #partstat.
|
527
|
+
def partstat=(status)
|
528
|
+
@field['partstat'] = status.to_str
|
529
|
+
status
|
530
|
+
end
|
531
|
+
|
532
|
+
# The value of the RSVP field, either +true+ or +false+. It is used to
|
533
|
+
# specify whether there is an expectation of a favor of a reply from the
|
534
|
+
# calendar user specified by the property value.
|
535
|
+
def rsvp
|
536
|
+
return false unless r = @field.param('RSVP')
|
537
|
+
r = r.first
|
538
|
+
return false unless r
|
539
|
+
case r
|
540
|
+
when /TRUE/i then true
|
541
|
+
when /FALSE/i then false
|
542
|
+
else raise InvalidEncodingError, "RSVP param value not TRUE/FALSE: #{r}"
|
543
|
+
end
|
544
|
+
end
|
545
|
+
end
|
546
|
+
end
|
547
|
+
end
|
548
|
+
|