fraser-vpim-rails 0.658

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. data/CHANGES +504 -0
  2. data/COPYING +58 -0
  3. data/README +182 -0
  4. data/lib/atom.rb +728 -0
  5. data/lib/plist.rb +22 -0
  6. data/lib/vpim/address.rb +219 -0
  7. data/lib/vpim/attachment.rb +102 -0
  8. data/lib/vpim/date.rb +222 -0
  9. data/lib/vpim/dirinfo.rb +277 -0
  10. data/lib/vpim/duration.rb +119 -0
  11. data/lib/vpim/enumerator.rb +32 -0
  12. data/lib/vpim/field.rb +614 -0
  13. data/lib/vpim/icalendar.rb +381 -0
  14. data/lib/vpim/maker/vcard.rb +16 -0
  15. data/lib/vpim/property/base.rb +193 -0
  16. data/lib/vpim/property/common.rb +315 -0
  17. data/lib/vpim/property/location.rb +38 -0
  18. data/lib/vpim/property/priority.rb +43 -0
  19. data/lib/vpim/property/recurrence.rb +69 -0
  20. data/lib/vpim/property/resources.rb +24 -0
  21. data/lib/vpim/repo.rb +181 -0
  22. data/lib/vpim/rfc2425.rb +367 -0
  23. data/lib/vpim/rrule.rb +589 -0
  24. data/lib/vpim/time.rb +40 -0
  25. data/lib/vpim/vcard.rb +1429 -0
  26. data/lib/vpim/version.rb +18 -0
  27. data/lib/vpim/vevent.rb +187 -0
  28. data/lib/vpim/view.rb +90 -0
  29. data/lib/vpim/vjournal.rb +58 -0
  30. data/lib/vpim/vpim.rb +65 -0
  31. data/lib/vpim/vtodo.rb +103 -0
  32. data/lib/vpim.rb +13 -0
  33. data/samples/README.mutt +93 -0
  34. data/samples/ab-query.rb +57 -0
  35. data/samples/cmd-itip.rb +156 -0
  36. data/samples/ex_cpvcard.rb +55 -0
  37. data/samples/ex_get_vcard_photo.rb +22 -0
  38. data/samples/ex_mkv21vcard.rb +34 -0
  39. data/samples/ex_mkvcard.rb +64 -0
  40. data/samples/ex_mkyourown.rb +29 -0
  41. data/samples/ics-dump.rb +210 -0
  42. data/samples/ics-to-rss.rb +84 -0
  43. data/samples/mutt-aliases-to-vcf.rb +45 -0
  44. data/samples/osx-wrappers.rb +86 -0
  45. data/samples/reminder.rb +203 -0
  46. data/samples/rrule.rb +71 -0
  47. data/samples/tabbed-file-to-vcf.rb +390 -0
  48. data/samples/vcf-dump.rb +86 -0
  49. data/samples/vcf-lines.rb +61 -0
  50. data/samples/vcf-to-ics.rb +22 -0
  51. data/samples/vcf-to-mutt.rb +121 -0
  52. data/test/test_all.rb +17 -0
  53. data/test/test_date.rb +120 -0
  54. data/test/test_dur.rb +41 -0
  55. data/test/test_field.rb +156 -0
  56. data/test/test_ical.rb +415 -0
  57. data/test/test_repo.rb +158 -0
  58. data/test/test_rrule.rb +1030 -0
  59. data/test/test_vcard.rb +973 -0
  60. data/test/test_view.rb +79 -0
  61. metadata +129 -0
@@ -0,0 +1,315 @@
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/address'
10
+ require 'vpim/attachment'
11
+
12
+ module Vpim
13
+ class Icalendar
14
+ module Property
15
+
16
+ # Properties common to Vevent, Vtodo, and Vjournal.
17
+ module Common
18
+
19
+ # This property defines the access classification for a calendar
20
+ # component.
21
+ #
22
+ # An access classification is only one component of the general
23
+ # security system within a calendar application. It provides a method
24
+ # of capturing the scope of the access the calendar owner intends for
25
+ # information within an individual calendar entry. The access
26
+ # classification of an individual iCalendar component is useful when
27
+ # measured along with the other security components of a calendar
28
+ # system (e.g., calendar user authentication, authorization, access
29
+ # rights, access role, etc.). Hence, the semantics of the individual
30
+ # access classifications cannot be completely defined by this memo
31
+ # alone. Additionally, due to the "blind" nature of most exchange
32
+ # processes using this memo, these access classifications cannot serve
33
+ # as an enforcement statement for a system receiving an iCalendar
34
+ # object. Rather, they provide a method for capturing the intention of
35
+ # the calendar owner for the access to the calendar component.
36
+ #
37
+ # Property Name: CLASS
38
+ #
39
+ # Property Value: one of "PUBLIC", "PRIVATE", "CONFIDENTIAL", default
40
+ # is "PUBLIC" if no CLASS property is found.
41
+ def access_class
42
+ proptoken 'CLASS', ["PUBLIC", "PRIVATE", "CONFIDENTIAL"], "PUBLIC"
43
+ end
44
+
45
+ def created
46
+ proptime 'CREATED'
47
+ end
48
+
49
+ # Description of the calendar component, or nil if there is no
50
+ # description.
51
+ def description
52
+ proptext 'DESCRIPTION'
53
+ end
54
+
55
+ # Revision sequence number of the calendar component, or nil if there
56
+ # is no SEQUENCE; property.
57
+ def sequence
58
+ propinteger 'SEQUENCE'
59
+ end
60
+
61
+ # The time stamp for this calendar component.
62
+ def dtstamp
63
+ proptime 'DTSTAMP'
64
+ end
65
+
66
+ # The start time for this calendar component.
67
+ def dtstart
68
+ proptime 'DTSTART'
69
+ end
70
+
71
+ def lastmod
72
+ proptime 'LAST-MODIFIED'
73
+ end
74
+
75
+ # Return the event organizer, an object of Icalendar::Address (or nil if
76
+ # there is no ORGANIZER field).
77
+ def organizer
78
+ organizer = @properties.field('ORGANIZER')
79
+
80
+ if organizer
81
+ organizer = Icalendar::Address.decode(organizer)
82
+ end
83
+
84
+ organizer.freeze
85
+ end
86
+
87
+ =begin
88
+ recurid
89
+ seq
90
+ =end
91
+
92
+ # Status values are not rejected during decoding. However, if the
93
+ # status is requested, and it's value is not one of the defined
94
+ # allowable values, an exception is raised.
95
+ def status
96
+ case self
97
+ when Vpim::Icalendar::Vevent
98
+ proptoken 'STATUS', ['TENTATIVE', 'CONFIRMED', 'CANCELLED']
99
+
100
+ when Vpim::Icalendar::Vtodo
101
+ proptoken 'STATUS', ['NEEDS-ACTION', 'COMPLETED', 'IN-PROCESS', 'CANCELLED']
102
+
103
+ when Vpim::Icalendar::Vevent
104
+ proptoken 'STATUS', ['DRAFT', 'FINAL', 'CANCELLED']
105
+ end
106
+ end
107
+
108
+ # TODO - def status? ...
109
+
110
+ # TODO - def status= ...
111
+
112
+ # Summary description of the calendar component, or nil if there is no
113
+ # SUMMARY property.
114
+ def summary
115
+ proptext 'SUMMARY'
116
+ end
117
+
118
+ # The unique identifier of this calendar component, a string.
119
+ def uid
120
+ proptext 'UID'
121
+ end
122
+
123
+ def url
124
+ propvalue 'URL'
125
+ end
126
+
127
+ # Return an array of attendees, an empty array if there are none. The
128
+ # attendees are objects of Icalendar::Address. If +uri+ is specified
129
+ # only the return the attendees with this +uri+.
130
+ def attendees(uri = nil)
131
+ attendees = @properties.enum_by_name('ATTENDEE').map { |a| Icalendar::Address.decode(a) }
132
+ attendees.freeze
133
+ if uri
134
+ attendees.select { |a| a == uri }
135
+ else
136
+ attendees
137
+ end
138
+ end
139
+
140
+ # Return true if the +uri+, usually a mailto: URI, is an attendee.
141
+ def attendee?(uri)
142
+ attendees.include? uri
143
+ end
144
+
145
+ # This property defines the categories for a calendar component.
146
+ #
147
+ # Property Name: CATEGORIES
148
+ #
149
+ # Value Type: TEXT
150
+ #
151
+ # Ruby Type: Array of String
152
+ #
153
+ # This property is used to specify categories or subtypes of the
154
+ # calendar component. The categories are useful in searching for a
155
+ # calendar component of a particular type and category.
156
+ def categories
157
+ proptextlistarray 'CATEGORIES'
158
+ end
159
+
160
+ def comments
161
+ proptextarray 'COMMENT'
162
+ end
163
+
164
+ def contacts
165
+ proptextarray 'CONTACT'
166
+ end
167
+
168
+ # An Array of attachments, see Attachment for more information.
169
+ def attachments
170
+ @properties.enum_by_name('ATTACH').map do |f|
171
+ attachment = Attachment.decode(f, 'uri', 'FMTTYPE')
172
+ end
173
+ end
174
+ end
175
+
176
+ end
177
+
178
+ module Set
179
+
180
+ # Properties common to Vevent, Vtodo, and Vjournal.
181
+ module Common
182
+
183
+ # Set the access class of the component, see Icalendar::Property::Common#access_class.
184
+ def access_class(token)
185
+ set_token 'CLASS', ["PUBLIC", "PRIVATE", "CONFIDENTIAL"], "PUBLIC", token
186
+ end
187
+
188
+ # Set the creation time, see Icalendar::Property::Common#created
189
+ def created(time)
190
+ set_datetime 'CREATED', time
191
+ end
192
+
193
+ # Set the description, see Icalendar::Property::Common#description.
194
+ def description(text)
195
+ set_text 'DESCRIPTION', text
196
+ end
197
+
198
+ # Set the sequence number, see Icalendar::Property::Common#sequence.
199
+ # is no SEQUENCE; property.
200
+ def sequence(int)
201
+ set_integer 'SEQUENCE', int
202
+ end
203
+
204
+ # Set the timestamp, see Icalendar::Property::Common#timestamp.
205
+ def dtstamp(time)
206
+ set_datetime 'DTSTAMP', time
207
+ self
208
+ end
209
+
210
+ # The start time or date, see Icalendar::Property::Common#dtstart.
211
+ def dtstart(start)
212
+ set_date_or_datetime 'DTSTART', 'DATE-TIME', start
213
+ self
214
+ end
215
+
216
+ # Set the last modification time, see Icalendar::Property::Common#lastmod.
217
+ def lastmod(time)
218
+ set_datetime 'LAST-MODIFIED', time
219
+ self
220
+ end
221
+
222
+ # Set the event organizer, an Icalendar::Address, see Icalendar::Property::Common#organizer.
223
+ #
224
+ # Without an +adr+ it yields an Icalendar::Address that is a copy of
225
+ # the current organizer (if any), allowing it to be modified.
226
+ def organizer(adr=nil) #:yield: organizer
227
+ unless adr
228
+ adr = @comp.organizer
229
+ if adr
230
+ adr = adr.copy
231
+ else
232
+ adr = Icalendar::Address.create
233
+ end
234
+ yield adr
235
+ end
236
+ set_address('ORGANIZER', adr)
237
+ self
238
+ end
239
+
240
+ =begin
241
+ # Status values are not rejected during decoding. However, if the
242
+ # status is requested, and it's value is not one of the defined
243
+ # allowable values, an exception is raised.
244
+ def status
245
+ case self
246
+ when Vpim::Icalendar::Vevent
247
+ proptoken 'STATUS', ['TENTATIVE', 'CONFIRMED', 'CANCELLED']
248
+
249
+ when Vpim::Icalendar::Vtodo
250
+ proptoken 'STATUS', ['NEEDS-ACTION', 'COMPLETED', 'IN-PROCESS', 'CANCELLED']
251
+
252
+ when Vpim::Icalendar::Vevent
253
+ proptoken 'STATUS', ['DRAFT', 'FINAL', 'CANCELLED']
254
+ end
255
+ end
256
+ =end
257
+
258
+ # Set summary description of component, see Icalendar::Property::Common#summary.
259
+ def summary(text)
260
+ set_text 'SUMMARY', text
261
+ end
262
+
263
+ # Set the unique identifier of this calendar component, see Icalendar::Property::Common#uid.
264
+ def uid(uid)
265
+ set_text 'UID', uid
266
+ end
267
+
268
+ def url(url)
269
+ set_text 'URL', url
270
+ end
271
+
272
+ # Add an attendee Address, see Icalendar::Property::Common#attendees.
273
+ def add_attendee(adr)
274
+ add_address('ATTENDEE', adr)
275
+ end
276
+
277
+ # Set the categories, see Icalendar::Property::Common#attendees.
278
+ #
279
+ # If +cats+ is provided, the categories are set to cats, either a
280
+ # String or an Array of String. Otherwise, and array of the existing
281
+ # category strings is yielded, and it can be modified.
282
+ def categories(cats = nil) #:yield: categories
283
+ unless cats
284
+ cats = @comp.categories
285
+ yield cats
286
+ end
287
+ # TODO - strip the strings
288
+ set_text_list('CATEGORIES', cats)
289
+ end
290
+
291
+ # Set the comment, see Icalendar::Property::Common#comments.
292
+ def comment(value)
293
+ set_text 'COMMENT', value
294
+ end
295
+
296
+ =begin
297
+ def contacts
298
+ proptextarray 'CONTACT'
299
+ end
300
+
301
+ # An Array of attachments, see Attachment for more information.
302
+ def attachments
303
+ @properties.enum_by_name('ATTACH').map do |f|
304
+ attachment = Attachment.decode(f, 'uri', 'FMTTYPE')
305
+ end
306
+ end
307
+ =end
308
+
309
+ end
310
+
311
+ end
312
+ end
313
+ end
314
+
315
+
@@ -0,0 +1,38 @@
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 Property
12
+ module Location
13
+ # Physical location information relevant to the component, or nil if
14
+ # there is no LOCATION property.
15
+ def location
16
+ proptext 'LOCATION'
17
+ end
18
+
19
+ # Array of Float, +[ latitude, longitude]+.
20
+ #
21
+ # North of the equator is positive latitude, east of the meridian is
22
+ # positive longitude.
23
+ #
24
+ # See RFC2445 for more info... there are lots of special cases.
25
+ def geo
26
+ prop = @properties.detect { |f| f.name? 'GEO' }
27
+ if prop
28
+ prop = Vpim.decode_list(prop.value_raw, ';') do |item| item.to_f end
29
+ end
30
+ prop
31
+ end
32
+
33
+ end
34
+ end
35
+ end
36
+ end
37
+
38
+
@@ -0,0 +1,43 @@
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 Property
12
+ module Priority
13
+
14
+ # +priority+ is a number from 1 to 9, with 1 being the highest
15
+ # priority, 9 being the lowest. 0 means "no priority", equivalent to
16
+ # not specifying the PRIORITY field.
17
+ #
18
+ # The other integer values are reserved by RFC2445.
19
+ #
20
+ # TODO
21
+ # - methods to compare priorities?
22
+ # - return as class Priority, with #to_i, and #to_s, and appropriate
23
+ # comparison operators?
24
+ def priority
25
+ p = @properties.detect { |f| f.name? 'PRIORITY' }
26
+
27
+ if !p
28
+ p = 0
29
+ else
30
+ p = p.value.to_i
31
+
32
+ if( p < 0 || p > 9 )
33
+ raise Vpim::InvalidEncodingError, 'Invalid priority #{@priority} - it must be 0-9!'
34
+ end
35
+ end
36
+ p
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+
43
+
@@ -0,0 +1,69 @@
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
+ module Vpim
12
+ class Icalendar
13
+ module Property
14
+
15
+ # Occurrences are calculated from DTSTART and RRULE. If there is no
16
+ # RRULE, the component occurs only once, at the start time.
17
+ #
18
+ # Limitations:
19
+ #
20
+ # Only a single RRULE: is currently supported, this is the most common
21
+ # case.
22
+ module Recurrence
23
+ def rrule #:nodoc:
24
+ start = dtstart
25
+ unless start
26
+ raise ArgumentError, "Components without a DTSTART don't have occurrences!"
27
+ end
28
+ Vpim::Rrule.new(start, propvalue('RRULE'))
29
+ end
30
+
31
+ # The times this components occurs. If a block is not provided, returns
32
+ # an enumerator.
33
+ #
34
+ # Occurrences may be infinite, +dountil+ can be provided to limit the
35
+ # iterations, see Rrule#each.
36
+ def occurrences(dountil = nil, &block) #:yield: occurrence time
37
+ rr = rrule
38
+ unless block_given?
39
+ return Enumerable::Enumerator.new(self, :occurrences, dountil)
40
+ end
41
+
42
+ rr.each(dountil, &block)
43
+ end
44
+
45
+ alias occurences occurrences #:nodoc: backwards compatibility
46
+
47
+ # True if this components occurs in a time period later than +t0+, but
48
+ # earlier than +t1+.
49
+ def occurs_in?(t0, t1)
50
+ # TODO - deprecate this, its a hack
51
+ occurrences(t1).detect do |tend|
52
+ if respond_to? :duration
53
+ tend += duration || 0
54
+ end
55
+ tend >= t0
56
+ end
57
+ end
58
+
59
+ def rdates #:nodoc:
60
+ # TODO - this is a hack, remove it
61
+ Vpim.decode_date_time_list(propvalue('RDATE'))
62
+ end
63
+
64
+ end
65
+ end
66
+ end
67
+ end
68
+
69
+
@@ -0,0 +1,24 @@
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 Property
12
+
13
+ module Resources
14
+
15
+ def resources
16
+ proptextlistarray 'RESOURCES'
17
+ end
18
+
19
+ end
20
+ end
21
+ end
22
+ end
23
+
24
+
data/lib/vpim/repo.rb ADDED
@@ -0,0 +1,181 @@
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 'plist'
12
+
13
+ require 'vpim/icalendar'
14
+ require 'vpim/duration'
15
+
16
+ module Vpim
17
+ # A Repo is a representation of a calendar repository.
18
+ #
19
+ # Currently supported repository types are:
20
+ # - Repo::Apple3, an Apple iCal3 repository.
21
+ # - Repo::Directory, a directory hierarchy containing .ics files
22
+ #
23
+ # All repository types support at least the methods of Repo, and all
24
+ # repositories return calendars that support at least the methods of
25
+ # Repo::Calendar.
26
+ class Repo
27
+ include Enumerable
28
+
29
+ # Open a repository at location +where+.
30
+ def initialize(where)
31
+ end
32
+
33
+ # Enumerate the calendars in the repository.
34
+ def each #:yield: calendar
35
+ end
36
+
37
+ # A calendar abstraction. It models a calendar in a calendar repository
38
+ # that may not be an iCalendar.
39
+ #
40
+ # It has methods that behave identically to Icalendar, but it also has
41
+ # methods like name and displayed that are not present in an iCalendar.
42
+ class Calendar
43
+ include Enumerable
44
+
45
+ # The calendar name.
46
+ def name
47
+ end
48
+
49
+ # Whether a calendar should be displayed.
50
+ #
51
+ # TODO - should be #displayed?
52
+ def displayed
53
+ end
54
+
55
+ # Encode into iCalendar format.
56
+ def encode
57
+ end
58
+
59
+ # Enumerate the components in the calendar, both todos and events, or
60
+ # the specified klass. Like Icalendar#each()
61
+ def each(klass=nil, &block) #:yield: component
62
+ end
63
+
64
+ # Enumerate the events in the calendar.
65
+ def events(&block) #:yield: Vevent
66
+ each(Vpim::Icalendar::Vevent, &block)
67
+ end
68
+
69
+ # Enumerate the todos in the calendar.
70
+ def todos(&block) #:yield: Vtodo
71
+ each(Vpim::Icalendar::Vtodo, &block)
72
+ end
73
+
74
+ # The method definitions are just to fool rdoc, not to be used.
75
+ %w{each name displayed encode}.each{|m| remove_method m}
76
+
77
+ def file_each(file, klass, &block) #:nodoc:
78
+ unless iterator?
79
+ return Enumerable::Enumerator.new(self, :each, klass)
80
+ end
81
+
82
+ cals = Vpim::Icalendar.decode(File.open(file))
83
+
84
+ cals.each do |cal|
85
+ cal.each(klass, &block)
86
+ end
87
+ self
88
+ end
89
+ end
90
+ end
91
+
92
+ class Repo
93
+ include Enumerable
94
+
95
+ # An Apple iCal version 3 repository.
96
+ class Apple3 < Repo
97
+ def initialize(where = "~/Library/Calendars")
98
+ @where = where.to_str
99
+ end
100
+
101
+ def each #:nodoc:
102
+ Dir[ File.expand_path(@where + "/**/*.calendar") ].each do |dir|
103
+ yield Calendar.new(dir)
104
+ end
105
+ self
106
+ end
107
+
108
+ class Calendar < Repo::Calendar
109
+ def initialize(dir) # :nodoc:
110
+ @dir = dir
111
+ end
112
+
113
+ def plist(key) #:nodoc:
114
+ Plist::parse_xml( @dir + "/Info.plist")[key]
115
+ end
116
+
117
+ def name #:nodoc:
118
+ plist "Title"
119
+ end
120
+
121
+ def displayed #:nodoc:
122
+ 1 == plist("Checked")
123
+ end
124
+
125
+ def each(klass=nil, &block) #:nodoc:
126
+ unless iterator?
127
+ return Enumerable::Enumerator.new(self, :each, klass)
128
+ end
129
+ Dir[ @dir + "/Events/*.ics" ].map do |ics|
130
+ file_each(ics, klass, &block)
131
+ end
132
+ self
133
+ end
134
+
135
+ def encode #:nodoc:
136
+ Icalendar.create2 do |cal|
137
+ each{|c| cal << c}
138
+ end.encode
139
+ end
140
+ end
141
+
142
+ end
143
+
144
+ class Directory < Repo
145
+ class Calendar < Repo::Calendar
146
+ def initialize(file) #:nodoc:
147
+ @file = file
148
+ end
149
+
150
+ def name #:nodoc:
151
+ File.basename(@file)
152
+ end
153
+
154
+ def displayed #:nodoc:
155
+ true
156
+ end
157
+
158
+ def each(klass, &block) #:nodoc:
159
+ file_each(@file, klass, &block)
160
+ end
161
+
162
+ def encode #:nodoc:
163
+ open(@file, "r"){|f| f.read}
164
+ end
165
+
166
+ end
167
+
168
+ def initialize(where = ".")
169
+ @where = where.to_str
170
+ end
171
+
172
+ def each #:nodoc:
173
+ Dir[ File.expand_path(@where + "/**/*.ics") ].each do |file|
174
+ yield Calendar.new(file)
175
+ end
176
+ self
177
+ end
178
+ end
179
+ end
180
+ end
181
+