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