vpim2 0.0.1
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.
- checksums.yaml +7 -0
- data/CHANGES +504 -0
- data/COPYING +58 -0
- data/README +182 -0
- data/lib/atom.rb +728 -0
- data/lib/plist.rb +22 -0
- data/lib/vpim.rb +13 -0
- data/lib/vpim/address.rb +219 -0
- data/lib/vpim/attachment.rb +102 -0
- data/lib/vpim/date.rb +222 -0
- data/lib/vpim/dirinfo.rb +277 -0
- data/lib/vpim/duration.rb +119 -0
- data/lib/vpim/enumerator.rb +32 -0
- data/lib/vpim/field.rb +614 -0
- data/lib/vpim/icalendar.rb +381 -0
- data/lib/vpim/maker/vcard.rb +16 -0
- data/lib/vpim/property/base.rb +193 -0
- data/lib/vpim/property/common.rb +315 -0
- data/lib/vpim/property/location.rb +38 -0
- data/lib/vpim/property/priority.rb +43 -0
- data/lib/vpim/property/recurrence.rb +69 -0
- data/lib/vpim/property/resources.rb +24 -0
- data/lib/vpim/repo.rb +181 -0
- data/lib/vpim/rfc2425.rb +367 -0
- data/lib/vpim/rrule.rb +591 -0
- data/lib/vpim/vcard.rb +1430 -0
- data/lib/vpim/version.rb +18 -0
- data/lib/vpim/vevent.rb +187 -0
- data/lib/vpim/view.rb +90 -0
- data/lib/vpim/vjournal.rb +58 -0
- data/lib/vpim/vpim.rb +65 -0
- data/lib/vpim/vtodo.rb +103 -0
- data/samples/README.mutt +93 -0
- data/samples/ab-query.rb +57 -0
- data/samples/cmd-itip.rb +156 -0
- data/samples/ex_cpvcard.rb +55 -0
- data/samples/ex_get_vcard_photo.rb +22 -0
- data/samples/ex_mkv21vcard.rb +34 -0
- data/samples/ex_mkvcard.rb +64 -0
- data/samples/ex_mkyourown.rb +29 -0
- data/samples/ics-dump.rb +210 -0
- data/samples/ics-to-rss.rb +84 -0
- data/samples/mutt-aliases-to-vcf.rb +45 -0
- data/samples/osx-wrappers.rb +86 -0
- data/samples/reminder.rb +203 -0
- data/samples/rrule.rb +71 -0
- data/samples/tabbed-file-to-vcf.rb +390 -0
- data/samples/vcf-dump.rb +86 -0
- data/samples/vcf-lines.rb +61 -0
- data/samples/vcf-to-ics.rb +22 -0
- data/samples/vcf-to-mutt.rb +121 -0
- data/test/test_all.rb +17 -0
- data/test/test_date.rb +120 -0
- data/test/test_dur.rb +41 -0
- data/test/test_field.rb +156 -0
- data/test/test_ical.rb +415 -0
- data/test/test_repo.rb +158 -0
- data/test/test_rrule.rb +1030 -0
- data/test/test_vcard.rb +973 -0
- data/test/test_view.rb +79 -0
- metadata +117 -0
@@ -0,0 +1,381 @@
|
|
1
|
+
=begin
|
2
|
+
Copyright (C) 2008 Sam Roberts
|
3
|
+
|
4
|
+
This library is free software; you can redistribute it and/or modify it
|
5
|
+
under the same terms as the ruby language itself, see the file COPYING for
|
6
|
+
details.
|
7
|
+
=end
|
8
|
+
|
9
|
+
require "enumerator"
|
10
|
+
|
11
|
+
require 'vpim/rfc2425'
|
12
|
+
require 'vpim/dirinfo'
|
13
|
+
require 'vpim/rrule'
|
14
|
+
require 'vpim/vevent'
|
15
|
+
require 'vpim/vtodo'
|
16
|
+
require 'vpim/vjournal'
|
17
|
+
require 'vpim/vpim'
|
18
|
+
|
19
|
+
module Vpim
|
20
|
+
# An iCalendar.
|
21
|
+
#
|
22
|
+
# A Calendar is some meta-information followed by a sequence of components.
|
23
|
+
#
|
24
|
+
# Defined components are Event, Todo, Freebusy, Journal, and Timezone, each
|
25
|
+
# of which are represented by their own class, though they share many
|
26
|
+
# properties in common. For example, Event and Todo may both contain
|
27
|
+
# multiple Alarm components.
|
28
|
+
#
|
29
|
+
# = Reference
|
30
|
+
#
|
31
|
+
# The iCalendar format is specified by a series of IETF documents:
|
32
|
+
#
|
33
|
+
# - link:rfc2445.txt: Internet Calendaring and Scheduling Core Object Specification
|
34
|
+
# - link:rfc2446.txt: iCalendar Transport-Independent Interoperability Protocol
|
35
|
+
# (iTIP) Scheduling Events, BusyTime, To-dos and Journal Entries
|
36
|
+
# - link:rfc2447.txt: iCalendar Message-Based Interoperability Protocol
|
37
|
+
#
|
38
|
+
# = iCalendar and vCalendar
|
39
|
+
#
|
40
|
+
# iCalendar files have VERSION:2.0 and vCalendar have VERSION:1.0. iCalendar
|
41
|
+
# (RFC 2445) is based on vCalendar, but but is not very compatible. While
|
42
|
+
# much appears to be similar, the recurrence rule syntax is completely
|
43
|
+
# different.
|
44
|
+
#
|
45
|
+
# iCalendars are usually transmitted in files with <code>.ics</code>
|
46
|
+
# extensions.
|
47
|
+
class Icalendar
|
48
|
+
# FIXME do NOT do this!
|
49
|
+
include Vpim
|
50
|
+
|
51
|
+
# Regular expression strings for the EBNF of RFC 2445
|
52
|
+
module Bnf #:nodoc:
|
53
|
+
# dur-value = ["+" / "-"] "P" [ 1*DIGIT "W" ] [ 1*DIGIT "D" ] [ "T" [ 1*DIGIT "H" ] [ 1*DIGIT "M" ] [ 1*DIGIT "S" ] ]
|
54
|
+
DURATION = '([-+])?P(\d+W)?(\d+D)?T?(\d+H)?(\d+M)?(\d+S)?'
|
55
|
+
end
|
56
|
+
|
57
|
+
private_class_method :new
|
58
|
+
|
59
|
+
# Create a new Icalendar object from +fields+, an array of
|
60
|
+
# DirectoryInfo::Field objects.
|
61
|
+
#
|
62
|
+
# When decoding Calendar data, you would usually use Icalendar.decode(),
|
63
|
+
# which decodes the data into the field arrays, and calls this method
|
64
|
+
# for each Calendar it finds.
|
65
|
+
def initialize(fields) #:nodoc:
|
66
|
+
# seperate into the outer-level fields, and the arrays of component
|
67
|
+
# fields
|
68
|
+
outer, inner = Vpim.outer_inner(fields)
|
69
|
+
|
70
|
+
# Make a dirinfo out of outer, and check its an iCalendar
|
71
|
+
@properties = DirectoryInfo.create(outer)
|
72
|
+
@properties.check_begin_end('VCALENDAR')
|
73
|
+
|
74
|
+
@components = []
|
75
|
+
|
76
|
+
# could use #constants instead of this
|
77
|
+
factory = {
|
78
|
+
'VEVENT' => Vevent,
|
79
|
+
'VTODO' => Vtodo,
|
80
|
+
'VJOURNAL' => Vjournal,
|
81
|
+
# TODO - VTIMEZONE
|
82
|
+
}
|
83
|
+
|
84
|
+
inner.each do |component|
|
85
|
+
name = component.first.value
|
86
|
+
|
87
|
+
if klass = factory[name]
|
88
|
+
@components << klass.new(component)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# Add an event to this calendar.
|
94
|
+
#
|
95
|
+
# Yields an event maker, Icalendar::Vevent::Maker.
|
96
|
+
def add_event(&block) #:yield:event
|
97
|
+
push Vevent::Maker.make( &block )
|
98
|
+
end
|
99
|
+
|
100
|
+
# TODO add_todo, add_journal
|
101
|
+
|
102
|
+
=begin
|
103
|
+
TODO
|
104
|
+
# Allows customization of calendar creation.
|
105
|
+
class Maker
|
106
|
+
def initialize #:nodoc:
|
107
|
+
@prodid = Vpim::PRODID
|
108
|
+
end
|
109
|
+
|
110
|
+
attr :prodid
|
111
|
+
end
|
112
|
+
=end
|
113
|
+
|
114
|
+
# The producer ID defaults to Vpim::PRODID but you can set it to something
|
115
|
+
# specific to your application.
|
116
|
+
def Icalendar.create2(producer = Vpim::PRODID) #:yield: self
|
117
|
+
# FIXME - make the primary API
|
118
|
+
di = DirectoryInfo.create( [ DirectoryInfo::Field.create('VERSION', '2.0') ], 'VCALENDAR' )
|
119
|
+
|
120
|
+
di.push_unique DirectoryInfo::Field.create('PRODID', producer.to_str)
|
121
|
+
di.push_unique DirectoryInfo::Field.create('CALSCALE', "Gregorian")
|
122
|
+
|
123
|
+
cal = new(di.to_a)
|
124
|
+
|
125
|
+
if block_given?
|
126
|
+
yield cal
|
127
|
+
end
|
128
|
+
|
129
|
+
cal
|
130
|
+
end
|
131
|
+
|
132
|
+
# Create a new Icalendar object with the minimal set of fields for a valid
|
133
|
+
# Calendar. If specified, +fields+ must be an array of
|
134
|
+
# DirectoryInfo::Field objects to add. They can override the the default
|
135
|
+
# Calendar fields, so, for example, this can be used to set a custom PRODID field.
|
136
|
+
def Icalendar.create(fields=[])
|
137
|
+
di = DirectoryInfo.create( [ DirectoryInfo::Field.create('VERSION', '2.0') ], 'VCALENDAR' )
|
138
|
+
|
139
|
+
DirectoryInfo::Field.create_array(fields).each { |f| di.push_unique f }
|
140
|
+
|
141
|
+
di.push_unique DirectoryInfo::Field.create('PRODID', Vpim::PRODID)
|
142
|
+
di.push_unique DirectoryInfo::Field.create('CALSCALE', "Gregorian")
|
143
|
+
|
144
|
+
new(di.to_a)
|
145
|
+
end
|
146
|
+
|
147
|
+
# Create a new Icalendar object with a protocol method of REPLY.
|
148
|
+
#
|
149
|
+
# Meeting requests, and such, are Calendar containers with a protocol
|
150
|
+
# method of REQUEST, and contains some number of Events, Todos, etc.,
|
151
|
+
# that may need replying to. In order to reply to any of these components
|
152
|
+
# of a request, you must first build a Calendar object to hold your reply
|
153
|
+
# components.
|
154
|
+
#
|
155
|
+
# This method builds the reply Calendar, you then will add to it replies
|
156
|
+
# to the specific components of the request Calendar that you are replying
|
157
|
+
# to. If you have any particular fields that you want to be in the
|
158
|
+
# Calendar, other than the defaults, then can be supplied as +fields+, an
|
159
|
+
# array of Field objects.
|
160
|
+
def Icalendar.create_reply(fields=[])
|
161
|
+
fields << DirectoryInfo::Field.create('METHOD', 'REPLY')
|
162
|
+
|
163
|
+
Icalendar.create(fields)
|
164
|
+
end
|
165
|
+
|
166
|
+
# Used during encoding.
|
167
|
+
def fields # :nodoc:
|
168
|
+
f = @properties.to_a
|
169
|
+
last = f.pop
|
170
|
+
# Use of #each means we won't encode components in our View, but also
|
171
|
+
# that we won't encode timezones... but we don't decode/support timezones
|
172
|
+
# anyhow, so fix later.
|
173
|
+
each { |c| f << c.fields }
|
174
|
+
f.push last
|
175
|
+
end
|
176
|
+
|
177
|
+
# Encode the Calendar as a string. The width is the maximum width of the
|
178
|
+
# encoded lines, it can be specified, but is better left to the default.
|
179
|
+
def encode(width=nil)
|
180
|
+
# We concatenate the fields of all objects, create a DirInfo, then
|
181
|
+
# encode it.
|
182
|
+
di = DirectoryInfo.create(self.fields.flatten)
|
183
|
+
di.encode(width)
|
184
|
+
end
|
185
|
+
|
186
|
+
alias to_s encode
|
187
|
+
|
188
|
+
# Push a calendar component onto the calendar.
|
189
|
+
def push(component)
|
190
|
+
case component
|
191
|
+
when Vevent, Vtodo, Vjournal
|
192
|
+
@components << component
|
193
|
+
else
|
194
|
+
raise ArgumentError, "can't add a #{component.type} to a calendar"
|
195
|
+
end
|
196
|
+
self
|
197
|
+
end
|
198
|
+
|
199
|
+
alias :<< :push
|
200
|
+
|
201
|
+
# Check if the protocol method is +method+
|
202
|
+
def protocol?(method)
|
203
|
+
Vpim::Methods.casecmp?(protocol, method)
|
204
|
+
end
|
205
|
+
|
206
|
+
def Icalendar.decode_duration(str) #:nodoc:
|
207
|
+
unless match = %r{\s*#{Bnf::DURATION}\s*}.match(str)
|
208
|
+
raise InvalidEncodingError, "duration not valid (#{str})"
|
209
|
+
end
|
210
|
+
dur = 0
|
211
|
+
|
212
|
+
# Remember: match[0] is the whole match string, match[1] is $1, etc.
|
213
|
+
|
214
|
+
# Week
|
215
|
+
if match[2]
|
216
|
+
dur = match[2].to_i
|
217
|
+
end
|
218
|
+
# Days
|
219
|
+
dur *= 7
|
220
|
+
if match[3]
|
221
|
+
dur += match[3].to_i
|
222
|
+
end
|
223
|
+
# Hours
|
224
|
+
dur *= 24
|
225
|
+
if match[4]
|
226
|
+
dur += match[4].to_i
|
227
|
+
end
|
228
|
+
# Minutes
|
229
|
+
dur *= 60
|
230
|
+
if match[5]
|
231
|
+
dur += match[5].to_i
|
232
|
+
end
|
233
|
+
# Seconds
|
234
|
+
dur *= 60
|
235
|
+
if match[6]
|
236
|
+
dur += match[6].to_i
|
237
|
+
end
|
238
|
+
|
239
|
+
if match[1] && match[1] == '-'
|
240
|
+
dur = -dur
|
241
|
+
end
|
242
|
+
|
243
|
+
dur
|
244
|
+
end
|
245
|
+
|
246
|
+
# Decode iCalendar data into an array of Icalendar objects.
|
247
|
+
#
|
248
|
+
# Since iCalendars are self-delimited (by a BEGIN:VCALENDAR and an
|
249
|
+
# END:VCALENDAR), multiple iCalendars can be concatenated into a single
|
250
|
+
# file.
|
251
|
+
#
|
252
|
+
# cal must be String or IO, or implement #each by returning
|
253
|
+
# each line in the input as those classes do.
|
254
|
+
def Icalendar.decode(cal, e = nil)
|
255
|
+
entities = Vpim.expand(Vpim.decode(cal))
|
256
|
+
|
257
|
+
# Since all iCalendars must have a begin/end, the top-level should
|
258
|
+
# consist entirely of entities/arrays, even if its a single iCalendar.
|
259
|
+
if entities.detect { |e| ! e.kind_of? Array }
|
260
|
+
raise "Not a valid iCalendar"
|
261
|
+
end
|
262
|
+
|
263
|
+
calendars = []
|
264
|
+
|
265
|
+
entities.each do |e|
|
266
|
+
calendars << new(e)
|
267
|
+
end
|
268
|
+
|
269
|
+
calendars
|
270
|
+
end
|
271
|
+
|
272
|
+
# The iCalendar version multiplied by 10 as an Integer. iCalendar must have
|
273
|
+
# a version of 20, and vCalendar must have a version of 10.
|
274
|
+
def version
|
275
|
+
v = @properties['VERSION']
|
276
|
+
|
277
|
+
unless v
|
278
|
+
raise InvalidEncodingError, "Invalid calendar, no version field!"
|
279
|
+
end
|
280
|
+
|
281
|
+
v = v.to_f * 10
|
282
|
+
v = v.to_i
|
283
|
+
end
|
284
|
+
|
285
|
+
# The value of the PRODID field, an unstructured string meant to
|
286
|
+
# identify the software which encoded the Calendar data.
|
287
|
+
def producer
|
288
|
+
#f = @properties.field('PRODID')
|
289
|
+
#f && f.to_text
|
290
|
+
@properties.text('PRODID').first
|
291
|
+
end
|
292
|
+
|
293
|
+
# The value of the METHOD field. Protocol methods are used when iCalendars
|
294
|
+
# are exchanged in a calendar messaging system, such as iTIP or iMIP. When
|
295
|
+
# METHOD is not specified, the Calendar object is merely being used to
|
296
|
+
# transport a snapshot of some calendar information; without the intention
|
297
|
+
# of conveying a scheduling semantic.
|
298
|
+
#
|
299
|
+
# Note that this method can't be called +method+, thats already a method of
|
300
|
+
# Object.
|
301
|
+
def protocol
|
302
|
+
m = @properties['METHOD']
|
303
|
+
m ? m.upcase : m
|
304
|
+
end
|
305
|
+
|
306
|
+
# The value of the CALSCALE: property, or "GREGORIAN" if CALSCALE: is not
|
307
|
+
# present.
|
308
|
+
#
|
309
|
+
# This is of academic interest only. There aren't any other calendar scales
|
310
|
+
# defined, and given that its hard enough just dealing with Gregorian
|
311
|
+
# calendars, there probably won't be.
|
312
|
+
def calscale
|
313
|
+
(@properties['CALSCALE'] || 'GREGORIAN').upcase
|
314
|
+
end
|
315
|
+
|
316
|
+
# The array of all supported calendar components. If a class is provided,
|
317
|
+
# return only the components of that class.
|
318
|
+
#
|
319
|
+
# If a block is provided, yield the components instead of returning them.
|
320
|
+
#
|
321
|
+
# Examples:
|
322
|
+
# calendar.components(Vpim::Icalendar::Vevent)
|
323
|
+
# => array of all calendar components
|
324
|
+
#
|
325
|
+
# calendar.components(Vpim::Icalendar::Vtodo) {|c| c... }
|
326
|
+
# => yield all todo components
|
327
|
+
#
|
328
|
+
# calendar.components {|c| c... }
|
329
|
+
# => yield all components
|
330
|
+
#
|
331
|
+
# Note - use of this is mildly deprecated in favour of #each, #events,
|
332
|
+
# #todos, #journals because those won't return timezones, and will return
|
333
|
+
# Enumerators if called without a block.
|
334
|
+
def components(klass=Object) #:yields:component
|
335
|
+
klass ||= Object
|
336
|
+
|
337
|
+
unless block_given?
|
338
|
+
return @components.select{|c| klass === c}.freeze
|
339
|
+
end
|
340
|
+
|
341
|
+
@components.each do |c|
|
342
|
+
if klass === c
|
343
|
+
yield c
|
344
|
+
end
|
345
|
+
end
|
346
|
+
self
|
347
|
+
end
|
348
|
+
|
349
|
+
include Enumerable
|
350
|
+
|
351
|
+
# Enumerate the top-level calendar components. Yields them if a block
|
352
|
+
# is provided, otherwise returns an Enumerator.
|
353
|
+
#
|
354
|
+
# This skips components that are only internally meaningful to iCalendar,
|
355
|
+
# such as timezone definitions.
|
356
|
+
def each(klass=nil, &block) # :yield: component
|
357
|
+
unless block
|
358
|
+
return Enumerable::Enumerator.new(self, :each, klass)
|
359
|
+
end
|
360
|
+
components(klass, &block)
|
361
|
+
end
|
362
|
+
|
363
|
+
# Short-hand for #each(Icalendar::Vevent).
|
364
|
+
def events(&block) #:yield: Vevent
|
365
|
+
each(Icalendar::Vevent, &block)
|
366
|
+
end
|
367
|
+
|
368
|
+
# Short-hand for #each(Icalendar::Vtodo).
|
369
|
+
def todos(&block) #:yield: Vtodo
|
370
|
+
each(Icalendar::Vtodo, &block)
|
371
|
+
end
|
372
|
+
|
373
|
+
# Short-hand for #each(Icalendar::Vjournal).
|
374
|
+
def journals(&block) #:yield: Vjournal
|
375
|
+
each(Icalendar::Vjournal, &block)
|
376
|
+
end
|
377
|
+
|
378
|
+
end
|
379
|
+
|
380
|
+
end
|
381
|
+
|
@@ -0,0 +1,16 @@
|
|
1
|
+
=begin
|
2
|
+
Copyright (C) 2008 Sam Roberts
|
3
|
+
|
4
|
+
This library is free software; you can redistribute it and/or modify it
|
5
|
+
under the same terms as the ruby language itself, see the file COPYING for
|
6
|
+
details.
|
7
|
+
=end
|
8
|
+
|
9
|
+
require 'vpim/vcard'
|
10
|
+
|
11
|
+
module Vpim
|
12
|
+
module Maker #:nodoc:backwards compat
|
13
|
+
Vcard = Vpim::Vcard::Maker #:nodoc:backwards compat
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
@@ -0,0 +1,193 @@
|
|
1
|
+
=begin
|
2
|
+
Copyright (C) 2008 Sam Roberts
|
3
|
+
|
4
|
+
This library is free software; you can redistribute it and/or modify it
|
5
|
+
under the same terms as the ruby language itself, see the file COPYING for
|
6
|
+
details.
|
7
|
+
=end
|
8
|
+
|
9
|
+
module Vpim
|
10
|
+
class Icalendar
|
11
|
+
module Set #:nodoc:
|
12
|
+
module Util #:nodoc:
|
13
|
+
# TODO - rename module to Private?
|
14
|
+
|
15
|
+
def rm_all(name)
|
16
|
+
rm = @comp.properties.select { |f| f.name? name }
|
17
|
+
rm.each { |f| @comp.properties.delete(f) }
|
18
|
+
end
|
19
|
+
|
20
|
+
def set_token(name, allowed, default, value) #:nodoc:
|
21
|
+
value = value.to_str
|
22
|
+
unless allowed.include?(value)
|
23
|
+
raise Vpim::Unencodeable, "Invalid #{name} value '#{value}'"
|
24
|
+
end
|
25
|
+
rm_all(name)
|
26
|
+
unless value == default
|
27
|
+
@comp.properties.push Vpim::DirectoryInfo::Field.create(name, value)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def field_create(name, value, default_value_type = nil, value_type = nil, params = {})
|
32
|
+
if value_type && value_type != default_value_type
|
33
|
+
params['VALUE'] = value_type
|
34
|
+
end
|
35
|
+
Vpim::DirectoryInfo::Field.create(name, value, params)
|
36
|
+
end
|
37
|
+
|
38
|
+
def set_date_or_datetime(name, default, value)
|
39
|
+
f = nil
|
40
|
+
case value
|
41
|
+
when Date
|
42
|
+
f = field_create(name, Vpim.encode_date(value), default, 'DATE')
|
43
|
+
when Time
|
44
|
+
f = field_create(name, Vpim.encode_date_time(value), default, 'DATE-TIME')
|
45
|
+
else
|
46
|
+
raise Vpim::Unencodeable, "Invalid #{name} value #{value.inspect}"
|
47
|
+
end
|
48
|
+
rm_all(name)
|
49
|
+
@comp.properties.push(f)
|
50
|
+
end
|
51
|
+
|
52
|
+
def set_datetime(name, value)
|
53
|
+
f = field_create(name, Vpim.encode_date_time(value))
|
54
|
+
rm_all(name)
|
55
|
+
@comp.properties.push(f)
|
56
|
+
end
|
57
|
+
|
58
|
+
def set_text(name, value)
|
59
|
+
f = field_create(name, Vpim.encode_text(value))
|
60
|
+
rm_all(name)
|
61
|
+
@comp.properties.push(f)
|
62
|
+
end
|
63
|
+
|
64
|
+
def set_text_list(name, value)
|
65
|
+
f = field_create(name, Vpim.encode_text_list(value))
|
66
|
+
rm_all(name)
|
67
|
+
@comp.properties.push(f)
|
68
|
+
end
|
69
|
+
|
70
|
+
def set_integer(name, value)
|
71
|
+
value = value.to_int.to_s
|
72
|
+
f = field_create(name, value)
|
73
|
+
rm_all(name)
|
74
|
+
@comp.properties.push(f)
|
75
|
+
end
|
76
|
+
|
77
|
+
def add_address(name, value)
|
78
|
+
f = value.encode(name)
|
79
|
+
@comp.properties.push(f)
|
80
|
+
end
|
81
|
+
|
82
|
+
def set_address(name, value)
|
83
|
+
rm_all(name)
|
84
|
+
add_address(name, value)
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
module Property #:nodoc:
|
91
|
+
|
92
|
+
# FIXME - rename Base to Util
|
93
|
+
module Base #:nodoc:
|
94
|
+
# Value of first property with name +name+
|
95
|
+
def propvalue(name) #:nodoc:
|
96
|
+
prop = @properties.detect { |f| f.name? name }
|
97
|
+
if prop
|
98
|
+
prop = prop.value
|
99
|
+
end
|
100
|
+
prop
|
101
|
+
end
|
102
|
+
|
103
|
+
# Array of values of all properties with name +name+
|
104
|
+
def propvaluearray(name) #:nodoc:
|
105
|
+
@properties.select{ |f| f.name? name }.map{ |p| p.value }
|
106
|
+
end
|
107
|
+
|
108
|
+
|
109
|
+
def propinteger(name) #:nodoc:
|
110
|
+
prop = @properties.detect { |f| f.name? name }
|
111
|
+
if prop
|
112
|
+
prop = Vpim.decode_integer(prop.value)
|
113
|
+
end
|
114
|
+
prop
|
115
|
+
end
|
116
|
+
|
117
|
+
def proptoken(name, allowed, default_token = nil) #:nodoc:
|
118
|
+
prop = propvalue(name)
|
119
|
+
|
120
|
+
if prop
|
121
|
+
prop = prop.to_str.upcase
|
122
|
+
unless allowed.include?(prop)
|
123
|
+
raise Vpim::InvalidEncodingError, "Invalid #{name} value '#{prop}'"
|
124
|
+
end
|
125
|
+
else
|
126
|
+
prop = default_token
|
127
|
+
end
|
128
|
+
|
129
|
+
prop
|
130
|
+
end
|
131
|
+
|
132
|
+
# Value as DATE-TIME or DATE of object of first property with name +name+
|
133
|
+
def proptime(name) #:nodoc:
|
134
|
+
prop = @properties.detect { |f| f.name? name }
|
135
|
+
if prop
|
136
|
+
prop = prop.to_time.first
|
137
|
+
end
|
138
|
+
prop
|
139
|
+
end
|
140
|
+
|
141
|
+
# Value as TEXT of first property with name +name+
|
142
|
+
def proptext(name) #:nodoc:
|
143
|
+
prop = @properties.detect { |f| f.name? name }
|
144
|
+
if prop
|
145
|
+
prop = prop.to_text
|
146
|
+
end
|
147
|
+
prop
|
148
|
+
end
|
149
|
+
|
150
|
+
# Array of values as TEXT of all properties with name +name+
|
151
|
+
def proptextarray(name) #:nodoc:
|
152
|
+
@properties.select{ |f| f.name? name }.map{ |p| p.to_text }
|
153
|
+
end
|
154
|
+
|
155
|
+
# Array of values as TEXT list of all properties with name +name+
|
156
|
+
def proptextlistarray(name) #:nodoc:
|
157
|
+
@properties.select{ |f| f.name? name }.map{ |p| Vpim.decode_text_list(p.value_raw) }.flatten
|
158
|
+
end
|
159
|
+
|
160
|
+
# Duration has "almost" the same definition for Event and Todo
|
161
|
+
def propduration(endfield)
|
162
|
+
dur = @properties.field 'DURATION'
|
163
|
+
dte = @properties.field endfield
|
164
|
+
|
165
|
+
if !dur
|
166
|
+
return nil unless dte
|
167
|
+
|
168
|
+
b = dtstart
|
169
|
+
e = send(endfield.downcase.to_sym)
|
170
|
+
|
171
|
+
return (e - b).to_i
|
172
|
+
end
|
173
|
+
|
174
|
+
Icalendar.decode_duration(dur.value_raw)
|
175
|
+
end
|
176
|
+
|
177
|
+
def propend(endfield)
|
178
|
+
dte = @properties.field endfield.to_s.upcase
|
179
|
+
if dte
|
180
|
+
dte.to_time.first
|
181
|
+
elsif duration
|
182
|
+
dtstart + duration
|
183
|
+
else
|
184
|
+
nil
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|