mumboe-vpim 0.7

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.
Files changed (69) hide show
  1. data/CHANGES +510 -0
  2. data/COPYING +58 -0
  3. data/README +185 -0
  4. data/lib/vpim/address.rb +219 -0
  5. data/lib/vpim/agent/atomize.rb +104 -0
  6. data/lib/vpim/agent/base.rb +73 -0
  7. data/lib/vpim/agent/calendars.rb +173 -0
  8. data/lib/vpim/agent/handler.rb +26 -0
  9. data/lib/vpim/agent/ics.rb +161 -0
  10. data/lib/vpim/attachment.rb +102 -0
  11. data/lib/vpim/date.rb +222 -0
  12. data/lib/vpim/dirinfo.rb +277 -0
  13. data/lib/vpim/duration.rb +119 -0
  14. data/lib/vpim/enumerator.rb +32 -0
  15. data/lib/vpim/field.rb +614 -0
  16. data/lib/vpim/icalendar.rb +384 -0
  17. data/lib/vpim/maker/vcard.rb +16 -0
  18. data/lib/vpim/property/base.rb +193 -0
  19. data/lib/vpim/property/common.rb +315 -0
  20. data/lib/vpim/property/location.rb +38 -0
  21. data/lib/vpim/property/priority.rb +43 -0
  22. data/lib/vpim/property/recurrence.rb +69 -0
  23. data/lib/vpim/property/resources.rb +24 -0
  24. data/lib/vpim/repo.rb +261 -0
  25. data/lib/vpim/rfc2425.rb +367 -0
  26. data/lib/vpim/rrule.rb +591 -0
  27. data/lib/vpim/time.rb +40 -0
  28. data/lib/vpim/vcard.rb +1456 -0
  29. data/lib/vpim/version.rb +18 -0
  30. data/lib/vpim/vevent.rb +187 -0
  31. data/lib/vpim/view.rb +90 -0
  32. data/lib/vpim/vjournal.rb +58 -0
  33. data/lib/vpim/vpim.rb +65 -0
  34. data/lib/vpim/vtodo.rb +103 -0
  35. data/lib/vpim.rb +13 -0
  36. data/samples/README.mutt +93 -0
  37. data/samples/ab-query.rb +57 -0
  38. data/samples/agent.ru +10 -0
  39. data/samples/cmd-itip.rb +156 -0
  40. data/samples/ex_cpvcard.rb +55 -0
  41. data/samples/ex_get_vcard_photo.rb +22 -0
  42. data/samples/ex_mkv21vcard.rb +34 -0
  43. data/samples/ex_mkvcard.rb +64 -0
  44. data/samples/ex_mkyourown.rb +29 -0
  45. data/samples/ics-dump.rb +210 -0
  46. data/samples/ics-to-rss.rb +84 -0
  47. data/samples/mutt-aliases-to-vcf.rb +45 -0
  48. data/samples/osx-wrappers.rb +86 -0
  49. data/samples/reminder.rb +209 -0
  50. data/samples/rrule.rb +71 -0
  51. data/samples/tabbed-file-to-vcf.rb +390 -0
  52. data/samples/vcf-dump.rb +86 -0
  53. data/samples/vcf-lines.rb +61 -0
  54. data/samples/vcf-to-ics.rb +22 -0
  55. data/samples/vcf-to-mutt.rb +121 -0
  56. data/test/test_agent_atomize.rb +84 -0
  57. data/test/test_agent_calendars.rb +128 -0
  58. data/test/test_agent_ics.rb +96 -0
  59. data/test/test_all.rb +17 -0
  60. data/test/test_date.rb +120 -0
  61. data/test/test_dur.rb +41 -0
  62. data/test/test_field.rb +156 -0
  63. data/test/test_ical.rb +437 -0
  64. data/test/test_misc.rb +13 -0
  65. data/test/test_repo.rb +129 -0
  66. data/test/test_rrule.rb +1030 -0
  67. data/test/test_vcard.rb +973 -0
  68. data/test/test_view.rb +79 -0
  69. metadata +140 -0
@@ -0,0 +1,277 @@
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/enumerator'
10
+ require 'vpim/field'
11
+ require 'vpim/rfc2425'
12
+ require 'vpim/vpim'
13
+
14
+ module Vpim
15
+ # An RFC 2425 directory info object.
16
+ #
17
+ # A directory information object is a sequence of fields. The basic
18
+ # structure of the object, and the way in which it is broken into fields
19
+ # is common to all profiles of the directory info type.
20
+ #
21
+ # A vCard, for example, is a specialization of a directory info object.
22
+ #
23
+ # - [RFC2425] the directory information framework (ftp://ftp.ietf.org/rfc/rfc2425.txt)
24
+ #
25
+ # Here's an example of encoding a simple vCard using the low-level APIs:
26
+ #
27
+ # card = Vpim::Vcard.create
28
+ # card << Vpim::DirectoryInfo::Field.create('EMAIL', 'user.name@example.com', 'TYPE' => 'INTERNET' )
29
+ # card << Vpim::DirectoryInfo::Field.create('URL', 'http://www.example.com/user' )
30
+ # card << Vpim::DirectoryInfo::Field.create('FN', 'User Name' )
31
+ # puts card.to_s
32
+ #
33
+ # Don't do it like that, use Vpim::Vcard::Maker.
34
+ class DirectoryInfo
35
+ include Enumerable
36
+
37
+ private_class_method :new
38
+
39
+ # Initialize a DirectoryInfo object from +fields+. If +profile+ is
40
+ # specified, check the BEGIN/END fields.
41
+ def initialize(fields, profile = nil) #:nodoc:
42
+ if fields.detect { |f| ! f.kind_of? DirectoryInfo::Field }
43
+ raise ArgumentError, 'fields must be an array of DirectoryInfo::Field objects'
44
+ end
45
+
46
+ @string = nil # this is used as a flag to indicate that recoding will be necessary
47
+ @fields = fields
48
+
49
+ check_begin_end(profile) if profile
50
+ end
51
+
52
+ # Decode +card+ into a DirectoryInfo object.
53
+ #
54
+ # +card+ may either be a something that is convertible to a string using
55
+ # #to_str or an Array of objects that can be joined into a string using
56
+ # #join("\n"), or an IO object (which will be read to end-of-file).
57
+ #
58
+ # The lines in the string may be delimited using IETF (CRLF) or Unix (LF) conventions.
59
+ #
60
+ # A DirectoryInfo is mutable, you can add new fields to it, see
61
+ # Vpim::DirectoryInfo::Field#create() for how to create a new Field.
62
+ #
63
+ # TODO: I don't believe this is ever used, maybe I can remove it.
64
+ def DirectoryInfo.decode(card) #:nodoc:
65
+ if card.respond_to? :to_str
66
+ string = card.to_str
67
+ elsif card.kind_of? Array
68
+ string = card.join("\n")
69
+ elsif card.kind_of? IO
70
+ string = card.read(nil)
71
+ else
72
+ raise ArgumentError, "DirectoryInfo cannot be created from a #{card.type}"
73
+ end
74
+
75
+ fields = Vpim.decode(string)
76
+
77
+ new(fields)
78
+ end
79
+
80
+ # Create a new DirectoryInfo object. The +fields+ are an optional array of
81
+ # DirectoryInfo::Field objects to add to the new object, between the
82
+ # BEGIN/END. If the +profile+ string is not nil, then it is the name of
83
+ # the directory info profile, and the BEGIN:+profile+/END:+profile+ fields
84
+ # will be added.
85
+ #
86
+ # A DirectoryInfo is mutable, you can add new fields to it using #push(),
87
+ # and see Field#create().
88
+ def DirectoryInfo.create(fields = [], profile = nil)
89
+
90
+ if profile
91
+ p = profile.to_str
92
+ f = [ Field.create('BEGIN', p) ]
93
+ f.concat fields
94
+ f.push Field.create('END', p)
95
+ fields = f
96
+ end
97
+
98
+ new(fields, profile)
99
+ end
100
+
101
+ # The first field named +name+, or nil if no
102
+ # match is found.
103
+ def field(name)
104
+ enum_by_name(name).each { |f| return f }
105
+ nil
106
+ end
107
+
108
+ # The value of the first field named +name+, or nil if no
109
+ # match is found.
110
+ def [](name)
111
+ enum_by_name(name).each { |f| return f.value if f.value != ''}
112
+ enum_by_name(name).each { |f| return f.value }
113
+ nil
114
+ end
115
+
116
+ # An array of all the values of fields named +name+, converted to text
117
+ # (using Field#to_text()).
118
+ #
119
+ # TODO - call this #texts(), as in the plural?
120
+ def text(name)
121
+ accum = []
122
+ each do |f|
123
+ if f.name? name
124
+ accum << f.to_text
125
+ end
126
+ end
127
+ accum
128
+ end
129
+
130
+ # Array of all the Field#group()s.
131
+ def groups
132
+ @fields.collect { |f| f.group } .compact.uniq
133
+ end
134
+
135
+ # All fields, frozen.
136
+ def fields #:nodoc:
137
+ @fields.dup.freeze
138
+ end
139
+
140
+ # Yields for each Field for which +cond+.call(field) is true. The
141
+ # (default) +cond+ of nil is considered true for all fields, so
142
+ # this acts like a normal #each() when called with no arguments.
143
+ def each(cond = nil) # :yields: Field
144
+ @fields.each do |field|
145
+ if(cond == nil || cond.call(field))
146
+ yield field
147
+ end
148
+ end
149
+ self
150
+ end
151
+
152
+ # Returns an Enumerator for each Field for which #name?(+name+) is true.
153
+ #
154
+ # An Enumerator supports all the methods of Enumerable, so it allows iteration,
155
+ # collection, mapping, etc.
156
+ #
157
+ # Examples:
158
+ #
159
+ # Print all the nicknames in a card:
160
+ #
161
+ # card.enum_by_name('NICKNAME') { |f| puts f.value }
162
+ #
163
+ # Print an Array of the preferred email addresses in the card:
164
+ #
165
+ # pref_emails = card.enum_by_name('EMAIL').select { |f| f.pref? }
166
+ def enum_by_name(name)
167
+ Enumerator.new(self, Proc.new { |field| field.name?(name) })
168
+ end
169
+
170
+ # Returns an Enumerator for each Field for which #group?(+group+) is true.
171
+ #
172
+ # For example, to print all the fields, sorted by group, you could do:
173
+ #
174
+ # card.groups.sort.each do |group|
175
+ # card.enum_by_group(group).each do |field|
176
+ # puts "#{group} -> #{field.name}"
177
+ # end
178
+ # end
179
+ #
180
+ # or to get an array of all the fields in group 'AGROUP', you could do:
181
+ #
182
+ # card.enum_by_group('AGROUP').to_a
183
+ def enum_by_group(group)
184
+ Enumerator.new(self, Proc.new { |field| field.group?(group) })
185
+ end
186
+
187
+ # Returns an Enumerator for each Field for which +cond+.call(field) is true.
188
+ def enum_by_cond(cond)
189
+ Enumerator.new(self, cond )
190
+ end
191
+
192
+ # Force card to be reencoded from the fields.
193
+ def dirty #:nodoc:
194
+ #string = nil
195
+ end
196
+
197
+ # Append +field+ to the fields. Note that it won't be literally appended
198
+ # to the fields, it will be inserted before the closing END field.
199
+ def push(field)
200
+ dirty
201
+ @fields[-1,0] = field
202
+ self
203
+ end
204
+
205
+ alias << push
206
+
207
+ # Push +field+ onto the fields, unless there is already a field
208
+ # with this name.
209
+ def push_unique(field)
210
+ push(field) unless @fields.detect { |f| f.name? field.name }
211
+ self
212
+ end
213
+
214
+ # Append +field+ to the end of all the fields. This isn't usually what you
215
+ # want to do, usually a DirectoryInfo's first and last fields are a
216
+ # BEGIN/END pair, see #push().
217
+ def push_end(field)
218
+ @fields << field
219
+ self
220
+ end
221
+
222
+ # Delete +field+.
223
+ #
224
+ # Warning: You can't delete BEGIN: or END: fields, but other
225
+ # profile-specific fields can be deleted, including mandatory ones. For
226
+ # vCards in particular, in order to avoid destroying them, I suggest
227
+ # creating a new Vcard, and copying over all the fields that you still
228
+ # want, rather than using #delete. This is easy with Vcard::Maker#copy, see
229
+ # the Vcard::Maker examples.
230
+ def delete(field)
231
+ case
232
+ when field.name?('BEGIN'), field.name?('END')
233
+ raise ArgumentError, 'Cannot delete BEGIN or END fields.'
234
+ else
235
+ @fields.delete field
236
+ end
237
+
238
+ self
239
+ end
240
+
241
+ # The string encoding of the DirectoryInfo. See Field#encode for information
242
+ # about the width parameter.
243
+ def encode(width=nil)
244
+ unless @string
245
+ @string = @fields.collect { |f| f.encode(width) } . join ""
246
+ end
247
+ @string
248
+ end
249
+
250
+ alias to_s encode
251
+
252
+ # Check that the DirectoryInfo object is correctly delimited by a BEGIN
253
+ # and END, that their profile values match, and if +profile+ is specified, that
254
+ # they are the specified profile.
255
+ def check_begin_end(profile=nil) #:nodoc:
256
+ unless @fields.first
257
+ raise "No fields to check"
258
+ end
259
+ unless @fields.first.name? 'BEGIN'
260
+ raise "Needs BEGIN, found: #{@fields.first.encode nil}"
261
+ end
262
+ unless @fields.last.name? 'END'
263
+ raise "Needs END, found: #{@fields.last.encode nil}"
264
+ end
265
+ unless @fields.last.value? @fields.first.value
266
+ raise "BEGIN/END mismatch: (#{@fields.first.value} != #{@fields.last.value}"
267
+ end
268
+ if profile
269
+ if ! @fields.first.value? profile
270
+ raise "Mismatched profile"
271
+ end
272
+ end
273
+ true
274
+ end
275
+ end
276
+ end
277
+
@@ -0,0 +1,119 @@
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 Duration
11
+ SECS_HOUR = 60 * 60
12
+ SECS_DAY = 24 * SECS_HOUR
13
+ MINS_HOUR = 60
14
+
15
+ # Initialize from a number of seconds.
16
+ def initialize(secs)
17
+ @secs = secs
18
+ end
19
+
20
+ def Duration.secs(secs)
21
+ Duration.new(secs)
22
+ end
23
+
24
+ def Duration.mins(mins)
25
+ Duration.new(mins * 60)
26
+ end
27
+
28
+ def Duration.hours(hours)
29
+ Duration.new(hours * SECS_HOUR)
30
+ end
31
+
32
+ def Duration.days(days)
33
+ Duration.new(days * SECS_DAY)
34
+ end
35
+
36
+ def secs
37
+ @secs
38
+ end
39
+
40
+ def mins
41
+ (@secs/60).to_i
42
+ end
43
+
44
+ def hours
45
+ (@secs/SECS_HOUR).to_i
46
+ end
47
+
48
+ def days
49
+ (@secs/SECS_DAY).to_i
50
+ end
51
+
52
+ def weeks
53
+ (days/7).to_i
54
+ end
55
+
56
+ def by_hours
57
+ [ hours, mins % MINS_HOUR, secs % 60]
58
+ end
59
+
60
+ def by_days
61
+ [ days, hours % 24, mins % MINS_HOUR, secs % 60]
62
+ end
63
+
64
+ def to_a
65
+ by_days
66
+ end
67
+
68
+ def to_s
69
+ Duration.as_str(self.to_a)
70
+ end
71
+
72
+ def Duration.as_str(arr)
73
+ s = ""
74
+ case arr.length
75
+ when 4
76
+ if arr[0] > 0
77
+ s << "#{arr[0]} days"
78
+ end
79
+ if arr[1] > 0
80
+ if s.length > 0
81
+ s << ', '
82
+ end
83
+ s << "#{arr[1]} hours"
84
+ end
85
+ if arr[2] > 0
86
+ if s.length > 0
87
+ s << ', '
88
+ end
89
+ s << "#{arr[2]} mins"
90
+ end
91
+ if arr[3] > 0
92
+ if s.length > 0
93
+ s << ', '
94
+ end
95
+ s << "#{arr[3]} secs"
96
+ end
97
+ when 3
98
+ if arr[0] > 0
99
+ s << "#{arr[0]} hours"
100
+ end
101
+ if arr[1] > 0
102
+ if s.length > 0
103
+ s << ', '
104
+ end
105
+ s << "#{arr[1]} mins"
106
+ end
107
+ if arr[2] > 0
108
+ if s.length > 0
109
+ s << ', '
110
+ end
111
+ s << "#{arr[2]} secs"
112
+ end
113
+ end
114
+
115
+ s
116
+ end
117
+ end
118
+ end
119
+
@@ -0,0 +1,32 @@
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
+ # This is a way for an object to have multiple ways of being enumerated via
13
+ # argument to it's #each() method. An Enumerator mixes in Enumerable, so the
14
+ # standard APIs such as Enumerable#map(), Enumerable#to_a(), and
15
+ # Enumerable#find_all() can be used on it.
16
+ #
17
+ # TODO since 1.8, this is part of the standard library, I should rewrite vPim
18
+ # so this can be removed.
19
+ class Enumerator
20
+ include Enumerable
21
+
22
+ def initialize(obj, *args)
23
+ @obj = obj
24
+ @args = args
25
+ end
26
+
27
+ def each(&block)
28
+ @obj.each(*@args, &block)
29
+ end
30
+ end
31
+ end
32
+