vpim 0.619 → 0.658

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.
@@ -16,7 +16,7 @@ class Date
16
16
 
17
17
  # Converts this object to a Time object, or throws an ArgumentError if
18
18
  # conversion is not possible because it is before the start of epoch.
19
- def to_time
19
+ def vpim_to_time
20
20
  raise ArgumentError, 'date is before the start of system time' if self < TIME_START
21
21
  days = self - TIME_START
22
22
 
@@ -6,11 +6,16 @@
6
6
  details.
7
7
  =end
8
8
 
9
+ require "enumerator"
10
+
9
11
  module Vpim
10
12
  # This is a way for an object to have multiple ways of being enumerated via
11
13
  # argument to it's #each() method. An Enumerator mixes in Enumerable, so the
12
- # standard APIS such as Enumerable#map(), Enumerable#to_a(), and
14
+ # standard APIs such as Enumerable#map(), Enumerable#to_a(), and
13
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.
14
19
  class Enumerator
15
20
  include Enumerable
16
21
 
@@ -98,6 +98,7 @@ module Vpim
98
98
  line << value
99
99
 
100
100
  else
101
+ # FIXME - somewhere along here, values with special chars need escaping...
101
102
  line << value.to_str
102
103
  end
103
104
  line
@@ -6,6 +6,8 @@
6
6
  details.
7
7
  =end
8
8
 
9
+ require "enumerator"
10
+
9
11
  require 'vpim/rfc2425'
10
12
  require 'vpim/dirinfo'
11
13
  require 'vpim/rrule'
@@ -43,6 +45,7 @@ module Vpim
43
45
  # iCalendars are usually transmitted in files with <code>.ics</code>
44
46
  # extensions.
45
47
  class Icalendar
48
+ # FIXME do NOT do this!
46
49
  include Vpim
47
50
 
48
51
  # Regular expression strings for the EBNF of RFC 2445
@@ -70,19 +73,16 @@ module Vpim
70
73
 
71
74
  @components = []
72
75
 
76
+ # could use #constants instead of this
73
77
  factory = {
74
78
  'VEVENT' => Vevent,
75
79
  'VTODO' => Vtodo,
76
80
  'VJOURNAL' => Vjournal,
81
+ # TODO - VTIMEZONE
77
82
  }
78
83
 
79
84
  inner.each do |component|
80
- name = component.first
81
- unless name.name? 'BEGIN'
82
- raise InvalidEncodingError, "calendar component begins with #{name.name}, instead of BEGIN!"
83
- end
84
-
85
- name = name.value
85
+ name = component.first.value
86
86
 
87
87
  if klass = factory[name]
88
88
  @components << klass.new(component)
@@ -90,25 +90,43 @@ module Vpim
90
90
  end
91
91
  end
92
92
 
93
- # Add and event to this calendar.
93
+ # Add an event to this calendar.
94
94
  #
95
95
  # Yields an event maker, Icalendar::Vevent::Maker.
96
96
  def add_event(&block) #:yield:event
97
97
  push Vevent::Maker.make( &block )
98
98
  end
99
99
 
100
- # FIXME - could take mandatory fields as an arguments
101
- # FIXME - args: support PRODID
102
- # FIXME - yield an Icalendar::Maker if block provided
103
- # FIXME - maker#prodid=
104
- def Icalendar.create2(args = nil)
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
105
117
  # FIXME - make the primary API
106
118
  di = DirectoryInfo.create( [ DirectoryInfo::Field.create('VERSION', '2.0') ], 'VCALENDAR' )
107
119
 
108
- di.push_unique DirectoryInfo::Field.create('PRODID', Vpim::PRODID)
120
+ di.push_unique DirectoryInfo::Field.create('PRODID', producer.to_str)
109
121
  di.push_unique DirectoryInfo::Field.create('CALSCALE', "Gregorian")
110
122
 
111
- new(di.to_a)
123
+ cal = new(di.to_a)
124
+
125
+ if block_given?
126
+ yield cal
127
+ end
128
+
129
+ cal
112
130
  end
113
131
 
114
132
  # Create a new Icalendar object with the minimal set of fields for a valid
@@ -149,7 +167,10 @@ module Vpim
149
167
  def fields # :nodoc:
150
168
  f = @properties.to_a
151
169
  last = f.pop
152
- @components.each { |c| f << c.fields }
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 }
153
174
  f.push last
154
175
  end
155
176
 
@@ -175,6 +196,8 @@ module Vpim
175
196
  self
176
197
  end
177
198
 
199
+ alias :<< :push
200
+
178
201
  # Check if the protocol method is +method+
179
202
  def protocol?(method)
180
203
  Vpim::Methods.casecmp?(protocol, method)
@@ -246,9 +269,8 @@ module Vpim
246
269
  calendars
247
270
  end
248
271
 
249
- # The iCalendar version multiplied by 10 as an Integer. If no VERSION field
250
- # is present (which is non-conformant), nil is returned. iCalendar must
251
- # have a version of 20, and vCalendar would have a version of 10.
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.
252
274
  def version
253
275
  v = @properties['VERSION']
254
276
 
@@ -284,11 +306,11 @@ module Vpim
284
306
  # The value of the CALSCALE: property, or "GREGORIAN" if CALSCALE: is not
285
307
  # present.
286
308
  #
287
- # This is of academic interest, really because there aren't any other
288
- # calendar scales defined, and given that its hard enough just dealing with
289
- # Gregorian calendars, there probably won't be.
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.
290
312
  def calscale
291
- proptext('CALSCALE') || 'GREGORIAN'
313
+ (@properties['CALSCALE'] || 'GREGORIAN').upcase
292
314
  end
293
315
 
294
316
  # The array of all supported calendar components. If a class is provided,
@@ -305,8 +327,12 @@ module Vpim
305
327
  #
306
328
  # calendar.components {|c| c... }
307
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.
308
334
  def components(klass=Object) #:yields:component
309
- # TODO - should this take an interval: t0,t1?
335
+ klass ||= Object
310
336
 
311
337
  unless block_given?
312
338
  return @components.select{|c| klass === c}.freeze
@@ -320,14 +346,33 @@ module Vpim
320
346
  self
321
347
  end
322
348
 
323
- # For backwards compatibility. Use #components.
324
- def events #:nodoc:
325
- components Icalendar::Vevent
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)
326
371
  end
327
372
 
328
- # For backwards compatibility. Use #components.
329
- def todos #:nodoc:
330
- components Icalendar::Vtodo
373
+ # Short-hand for #each(Icalendar::Vjournal).
374
+ def journals(&block) #:yield: Vjournal
375
+ each(Icalendar::Vjournal, &block)
331
376
  end
332
377
 
333
378
  end
@@ -10,6 +10,7 @@ module Vpim
10
10
  class Icalendar
11
11
  module Set #:nodoc:
12
12
  module Util #:nodoc:
13
+ # TODO - rename module to Private?
13
14
 
14
15
  def rm_all(name)
15
16
  rm = @comp.properties.select { |f| f.name? name }
@@ -114,7 +115,7 @@ module Vpim
114
115
  end
115
116
 
116
117
  def proptoken(name, allowed, default_token = nil) #:nodoc:
117
- prop = propvalue name
118
+ prop = propvalue(name)
118
119
 
119
120
  if prop
120
121
  prop = prop.to_str.upcase
@@ -156,6 +157,35 @@ module Vpim
156
157
  @properties.select{ |f| f.name? name }.map{ |p| Vpim.decode_text_list(p.value_raw) }.flatten
157
158
  end
158
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
+
159
189
  end
160
190
  end
161
191
  end
@@ -180,46 +180,46 @@ seq
180
180
  # Properties common to Vevent, Vtodo, and Vjournal.
181
181
  module Common
182
182
 
183
- # Set the access class of the component, see Icalendar::Get::Common#access_class.
183
+ # Set the access class of the component, see Icalendar::Property::Common#access_class.
184
184
  def access_class(token)
185
185
  set_token 'CLASS', ["PUBLIC", "PRIVATE", "CONFIDENTIAL"], "PUBLIC", token
186
186
  end
187
187
 
188
- # Set the creation time, see Icalendar::Get::Common#created
188
+ # Set the creation time, see Icalendar::Property::Common#created
189
189
  def created(time)
190
190
  set_datetime 'CREATED', time
191
191
  end
192
192
 
193
- # Set the description, see Icalendar::Get::Common#description.
193
+ # Set the description, see Icalendar::Property::Common#description.
194
194
  def description(text)
195
195
  set_text 'DESCRIPTION', text
196
196
  end
197
197
 
198
- # Set the sequence number, see Icalendar::Get::Common#sequence.
198
+ # Set the sequence number, see Icalendar::Property::Common#sequence.
199
199
  # is no SEQUENCE; property.
200
200
  def sequence(int)
201
201
  set_integer 'SEQUENCE', int
202
202
  end
203
203
 
204
- # Set the timestamp, see Icalendar::Get::Common#timestamp.
204
+ # Set the timestamp, see Icalendar::Property::Common#timestamp.
205
205
  def dtstamp(time)
206
206
  set_datetime 'DTSTAMP', time
207
207
  self
208
208
  end
209
209
 
210
- # The start time or date, see Icalendar::Get::Common#dtstart.
210
+ # The start time or date, see Icalendar::Property::Common#dtstart.
211
211
  def dtstart(start)
212
212
  set_date_or_datetime 'DTSTART', 'DATE-TIME', start
213
213
  self
214
214
  end
215
215
 
216
- # Set the last modification time, see Icalendar::Get::Common#lastmod.
216
+ # Set the last modification time, see Icalendar::Property::Common#lastmod.
217
217
  def lastmod(time)
218
218
  set_datetime 'LAST-MODIFIED', time
219
219
  self
220
220
  end
221
221
 
222
- # Set the event organizer, an Icalendar::Address, see Icalendar::Get::Common#organizer.
222
+ # Set the event organizer, an Icalendar::Address, see Icalendar::Property::Common#organizer.
223
223
  #
224
224
  # Without an +adr+ it yields an Icalendar::Address that is a copy of
225
225
  # the current organizer (if any), allowing it to be modified.
@@ -255,12 +255,12 @@ seq
255
255
  end
256
256
  =end
257
257
 
258
- # Set summary description of component, see Icalendar::Get::Common#summary.
258
+ # Set summary description of component, see Icalendar::Property::Common#summary.
259
259
  def summary(text)
260
260
  set_text 'SUMMARY', text
261
261
  end
262
262
 
263
- # Set the unique identifier of this calendar component, see Icalendar::Get::Common#uid.
263
+ # Set the unique identifier of this calendar component, see Icalendar::Property::Common#uid.
264
264
  def uid(uid)
265
265
  set_text 'UID', uid
266
266
  end
@@ -269,12 +269,12 @@ seq
269
269
  set_text 'URL', url
270
270
  end
271
271
 
272
- # Add an attendee Address, see Icalendar::Get::Common#attendees.
272
+ # Add an attendee Address, see Icalendar::Property::Common#attendees.
273
273
  def add_attendee(adr)
274
274
  add_address('ATTENDEE', adr)
275
275
  end
276
276
 
277
- # Set the categories, see Icalendar::Get::Common#attendees.
277
+ # Set the categories, see Icalendar::Property::Common#attendees.
278
278
  #
279
279
  # If +cats+ is provided, the categories are set to cats, either a
280
280
  # String or an Array of String. Otherwise, and array of the existing
@@ -288,7 +288,7 @@ seq
288
288
  set_text_list('CATEGORIES', cats)
289
289
  end
290
290
 
291
- # Set the comment, see Icalendar::Get::Common#comments.
291
+ # Set the comment, see Icalendar::Property::Common#comments.
292
292
  def comment(value)
293
293
  set_text 'COMMENT', value
294
294
  end
@@ -6,42 +6,58 @@
6
6
  details.
7
7
  =end
8
8
 
9
+ require "enumerator"
10
+
9
11
  module Vpim
10
12
  class Icalendar
11
13
  module Property
12
14
 
13
- # Occurrences are calculated from DTSTART: and RRULE:. If there is not
14
- # RRULE:, the component recurs only once, at the start time.
15
+ # Occurrences are calculated from DTSTART and RRULE. If there is no
16
+ # RRULE, the component occurs only once, at the start time.
15
17
  #
16
18
  # Limitations:
17
19
  #
18
20
  # Only a single RRULE: is currently supported, this is the most common
19
21
  # case.
20
- #
21
- # Implementation of multiple RRULE:s, and RDATE:, EXRULE:, and EXDATE: is
22
- # on the todo list. Its not a very high priority, because I haven't seen
23
- # calendars using the full range of recurrence features, and haven't
24
- # received feedback from any users requesting these features. So, if you
25
- # need it, contact me and implementation will get on the schedule.
26
22
  module Recurrence
27
- # The times this event occurs, as a Vpim::Rrule.
28
- def occurrences
23
+ def rrule #:nodoc:
29
24
  start = dtstart
30
25
  unless start
31
- raise ArgumentError, "Components with no DTSTART: don't have occurrences!"
26
+ raise ArgumentError, "Components without a DTSTART don't have occurrences!"
32
27
  end
33
28
  Vpim::Rrule.new(start, propvalue('RRULE'))
34
29
  end
35
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
+
36
45
  alias occurences occurrences #:nodoc: backwards compatibility
37
46
 
38
- # Check if this event overlaps with the time period later than or equal to +t0+, but
47
+ # True if this components occurs in a time period later than +t0+, but
39
48
  # earlier than +t1+.
40
49
  def occurs_in?(t0, t1)
41
- occurrences.each_until(t1).detect { |t| tend = t + (duration || 0); tend > t0 }
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
42
57
  end
43
58
 
44
- def rdates
59
+ def rdates #:nodoc:
60
+ # TODO - this is a hack, remove it
45
61
  Vpim.decode_date_time_list(propvalue('RDATE'))
46
62
  end
47
63
 
@@ -6,109 +6,174 @@
6
6
  details.
7
7
  =end
8
8
 
9
+ require 'enumerator'
10
+
11
+ require 'plist'
12
+
9
13
  require 'vpim/icalendar'
10
14
  require 'vpim/duration'
11
15
 
12
16
  module Vpim
13
- # A Repo is a representation of an event repository. Currently iCalv3
14
- # repositories and directories containing .ics files are supported.
17
+ # A Repo is a representation of a calendar repository.
15
18
  #
16
- # TODO - should yield them if a block is given, or return
17
- # an enumerable otherwise. Later.
18
- module Repo
19
- def self.somethings_from_file(something, file) #:nodoc:
20
- somethings = []
21
- begin
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
+
22
82
  cals = Vpim::Icalendar.decode(File.open(file))
23
83
 
24
84
  cals.each do |cal|
25
- cal.send(something).each do |x|
26
- somethings << x
27
- end
85
+ cal.each(klass, &block)
28
86
  end
87
+ self
29
88
  end
30
- somethings
31
- end
32
-
33
- def self.events_from_file(file) #:nodoc:
34
- self.somethings_from_file("events", file)
35
89
  end
90
+ end
36
91
 
37
- def self.todos_from_file(file) #:nodoc:
38
- self.somethings_from_file("todos", file)
39
- end
92
+ class Repo
93
+ include Enumerable
40
94
 
41
95
  # An Apple iCal version 3 repository.
42
- module Ical3
43
- class Calendar
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
44
109
  def initialize(dir) # :nodoc:
45
110
  @dir = dir
46
111
  end
112
+
47
113
  def plist(key) #:nodoc:
48
- Plist::parse_xml( @dir + "/Info.plist")[key]
114
+ Plist::parse_xml( @dir + "/Info.plist")[key]
49
115
  end
50
116
 
51
- # The calendar name.
52
- def name
117
+ def name #:nodoc:
53
118
  plist "Title"
54
119
  end
55
120
 
56
- # Whether a calendar should be displayed.
57
- def displayed
121
+ def displayed #:nodoc:
58
122
  1 == plist("Checked")
59
123
  end
60
124
 
61
- # Array of all events defined in the calendar.
62
- def events #:yield: Vevent
125
+ def each(klass=nil, &block) #:nodoc:
126
+ unless iterator?
127
+ return Enumerable::Enumerator.new(self, :each, klass)
128
+ end
63
129
  Dir[ @dir + "/Events/*.ics" ].map do |ics|
64
- Repo.events_from_file(ics)
65
- end.flatten
130
+ file_each(ics, klass, &block)
131
+ end
132
+ self
66
133
  end
67
134
 
68
- # Array of all todos defined in the calendar.
69
- def todos #:yield: Vevent
70
- Dir[ @dir + "/Events/*.ics" ].map do |ics|
71
- Repo.todos_from_file(ics)
72
- end.flatten
135
+ def encode #:nodoc:
136
+ Icalendar.create2 do |cal|
137
+ each{|c| cal << c}
138
+ end.encode
73
139
  end
74
-
75
140
  end
76
141
 
77
- def self.each(where = "~/Library/Calendars") # :yield: Apple::Calendar
78
- Dir[ File.expand_path(where + "/**/*.calendar") ].each do |dir|
79
- yield Calendar.new(dir)
80
- end
81
- self
82
- end
83
142
  end
84
- module Directory
85
- class Calendar
143
+
144
+ class Directory < Repo
145
+ class Calendar < Repo::Calendar
86
146
  def initialize(file) #:nodoc:
87
147
  @file = file
88
148
  end
89
149
 
90
- def name
150
+ def name #:nodoc:
91
151
  File.basename(@file)
92
152
  end
93
153
 
94
- def displayed
154
+ def displayed #:nodoc:
95
155
  true
96
156
  end
97
157
 
98
- def events
99
- Repo.events_from_file(@file)
158
+ def each(klass, &block) #:nodoc:
159
+ file_each(@file, klass, &block)
100
160
  end
101
161
 
102
- def todos
103
- Repo.todos_from_file(@file)
162
+ def encode #:nodoc:
163
+ open(@file, "r"){|f| f.read}
104
164
  end
165
+
166
+ end
167
+
168
+ def initialize(where = ".")
169
+ @where = where.to_str
105
170
  end
106
171
 
107
- def self.each(where)
108
- Dir[ File.expand_path(where + "/**/*.ics") ].each do |file|
109
- yield Calendar.new(file)
110
- end
111
- self
172
+ def each #:nodoc:
173
+ Dir[ File.expand_path(@where + "/**/*.ics") ].each do |file|
174
+ yield Calendar.new(file)
175
+ end
176
+ self
112
177
  end
113
178
  end
114
179
  end