vpim 0.619 → 0.658

Sign up to get free protection for your applications and to get access to all the features.
@@ -14,9 +14,10 @@ module Vpim
14
14
 
15
15
  # 1*(ALPHA / DIGIT / "-")
16
16
  # Note: I think I can add A-Z here, and get rid of the "i" matches elsewhere.
17
- # Note: added '_' to allowed because its produced by Notes - X-LOTUS-CHILD_UID
18
- # Note: added '/' to allowed because its produced by KAddressBook - X-messaging/xmpp-All:
19
- NAME = '[-a-z0-9_/]+'
17
+ # Note: added '_' to allowed because its produced by Notes (X-LOTUS-CHILD_UID:)
18
+ # Note: added '/' to allowed because its produced by KAddressBook (X-messaging/xmpp-All:)
19
+ # Note: added ' ' to allowed because its produced by highrisehq.com (X-GOOGLE TALK:)
20
+ NAME = '[-a-z0-9_/][-a-z0-9_/ ]*'
20
21
 
21
22
  # <"> <Any character except CTLs, DQUOTE> <">
22
23
  QSTR = '"([^"]*)"'
@@ -348,7 +349,7 @@ module Vpim
348
349
  # an array of all the inner arrays of fields. Return the array [outer,
349
350
  # inner].
350
351
  def Vpim.outer_inner(fields) #:nodoc:
351
- # FIXME - use Enumerable#partition
352
+ # TODO - use Enumerable#partition
352
353
  # seperate into the outer-level fields, and the arrays of component
353
354
  # fields
354
355
  outer = []
@@ -57,11 +57,10 @@ module Vpim
57
57
  # from a start time, +dtstart+ (which must the first of the set of
58
58
  # recurring times). If +rrule+ is nil, the set contains only +dtstart+.
59
59
  def initialize(dtstart, rrule = nil)
60
- # dtstart must be in local time, they say, but I think that really
61
- # means must be in a particular timezone
62
-
63
- # Note: DTSTART is always in the recurrence set
64
- @dtstart = dtstart
60
+ @dtstart = dtstart.getlocal
61
+ # The getlocal is a hack so that UTC times get converted to local,
62
+ # because yielded times are always local, because we don't support
63
+ # timezones.
65
64
  @rrule = rrule
66
65
 
67
66
  # Freq is mandatory, but must occur only once.
@@ -511,7 +510,7 @@ module Vpim
511
510
  dates
512
511
  end
513
512
 
514
- def byday_in_weekly(year, mon, day, wkst, byday)
513
+ def byday_in_weekly(year, mon, day, wkst, byday) #:nodoc:
515
514
  # debug ["day", year,mon,day,wkst,byday]
516
515
  days = byday.map{ |_, byday| Date.str2wday(byday) }
517
516
  week = DateGen.weekofdate(year, mon, day, wkst)
@@ -522,7 +521,71 @@ module Vpim
522
521
  week
523
522
  end
524
523
 
525
- end
524
+ # Help encode an RRULE value.
525
+ #
526
+ # TODO - the Maker is both incomplete, and its a bit cheesy, I'd like to do
527
+ # something that is a kind of programmatic version of the UI that iCal has.
528
+ class Maker
529
+ def initialize(&block) #:yield: self
530
+ @freq = nil
531
+ @until = nil
532
+ @count = nil
533
+ @interval = nil
534
+ @wkst = nil
535
+ @by = {}
536
+
537
+ if block
538
+ yield self
539
+ end
540
+ end
541
+
542
+ FREQ = %w{ YEARLY WEEKLY MONTHLY DAILY } #:nodoc: incomplete!
543
+
544
+ def frequency=(freq)
545
+ freq = freq.to_str.upcase
546
+ unless FREQ.include? freq
547
+ raise ArgumentError, "Frequency #{freq} is not valid"
548
+ end
549
+ @freq = freq
550
+ end
551
+
552
+ # +runtil+ is Time, Date, or DateTime
553
+ def until=(runtil)
554
+ if @count
555
+ raise ArgumentError, "Cannot specify UNTIL if COUNT was specified"
556
+ end
557
+ @until = runtil
558
+ end
526
559
 
560
+ # +count+ is integral
561
+ def count=(rcount)
562
+ if @until
563
+ raise ArgumentError, "Cannot specify COUNT if UNTIL was specified"
564
+ end
565
+ @count = rcount.to_int
566
+ end
567
+
568
+ # TODO - BY....
569
+
570
+ def encode
571
+ unless @freq
572
+ raise ArgumentError, "Must specify FREQUENCY"
573
+ end
574
+
575
+ rrule = "FREQ=#{@freq}"
576
+
577
+ [
578
+ ["COUNT", @count],
579
+ ["UNTIL", @until],
580
+ # TODO...
581
+ ].each do |k,v|
582
+ if v
583
+ rrule += ";#{k}=#{v}"
584
+ end
585
+ end
586
+ rrule
587
+ end
588
+ end
589
+ end
527
590
  end
528
591
 
@@ -868,7 +868,7 @@ module Vpim
868
868
  #
869
869
  # LOGO is a graphic image of a logo associated with the object the vCard
870
870
  # represents. Its not common, but would probably be equivalent to the logo
871
- # on printed card.
871
+ # on a printed card.
872
872
  #
873
873
  # See Attachment for a description of the value.
874
874
  def logos(&proc) #:yield: Line.value
@@ -958,7 +958,12 @@ module Vpim
958
958
  values('TEL')
959
959
  end
960
960
 
961
- ## TITLE
961
+ # The TITLE value, a text string specifying the job title, functional
962
+ # position, or function of the object the card represents. A wrapper around
963
+ # #value('TITLE').
964
+ def title
965
+ value('TITLE')
966
+ end
962
967
 
963
968
  ## UID
964
969
 
@@ -1334,6 +1339,25 @@ module Vpim
1334
1339
  self
1335
1340
  end
1336
1341
 
1342
+ # Set the title field, TITLE.
1343
+ #
1344
+ # It can be set to a single String.
1345
+ def title=(title)
1346
+ delete_if { |l| l.name == 'TITLE' }
1347
+
1348
+ @card << Vpim::DirectoryInfo::Field.create( 'TITLE', title );
1349
+ end
1350
+
1351
+ # Set the org field, ORG.
1352
+ #
1353
+ # It can be set to a single String or an Array of String.
1354
+ def org=(org)
1355
+ delete_if { |l| l.name == 'ORG' }
1356
+
1357
+ @card << Vpim::DirectoryInfo::Field.create( 'ORG', org );
1358
+ end
1359
+
1360
+
1337
1361
  # Add a URL field, URL.
1338
1362
  def add_url(url)
1339
1363
  @card << Vpim::DirectoryInfo::Field.create( 'URL', url.to_str );
@@ -7,9 +7,9 @@
7
7
  =end
8
8
 
9
9
  module Vpim
10
- PRODID = '-//Ensemble Independent//vPim 0.619//EN'
10
+ PRODID = '-//Ensemble Independent//vPim 0.658//EN'
11
11
 
12
- VERSION = '0.619'
12
+ VERSION = '0.658'
13
13
 
14
14
  # Return the API version as a string.
15
15
  def Vpim.version
@@ -9,6 +9,7 @@
9
9
  require 'vpim/dirinfo'
10
10
  require 'vpim/field'
11
11
  require 'vpim/rfc2425'
12
+ require 'vpim/rrule'
12
13
  require 'vpim/vpim'
13
14
  require 'vpim/property/base'
14
15
  require 'vpim/property/common'
@@ -38,7 +39,7 @@ module Vpim
38
39
  # See "TODO - fields" in dirinfo.rb
39
40
  end
40
41
 
41
- # TODO - derive everything from Icalendar::Component to get this kind of stuff?
42
+ # TODO - derive everything from Icalendar::Component to get rid of this kind of stuff?
42
43
  def fields #:nodoc:
43
44
  f = @properties.to_a
44
45
  last = f.pop
@@ -50,10 +51,8 @@ module Vpim
50
51
  @properties
51
52
  end
52
53
 
53
-
54
54
  # Create a new Vevent object. All events must have a DTSTART field,
55
55
  # specify it as either a Time or a Date in +start+, it defaults to "now"
56
- # (is this useful?).
57
56
  #
58
57
  # If specified, +fields+ must be either an array of Field objects to
59
58
  # add, or a Hash of String names to values that will be used to build
@@ -62,10 +61,13 @@ module Vpim
62
61
  #
63
62
  # Vevent.create(Date.today, 'SUMMARY' => "today's event")
64
63
  #
65
- # TODO - maybe events are usually created in a particular way? With a
66
- # start/duration or a start/end? Maybe I can make it easier. Ideally, I
67
- # would like to make it hard to encode an invalid Event.
68
64
  def Vevent.create(start = Time.now, fields=[])
65
+ # TODO
66
+ # - maybe events are usually created in a particular way? With a
67
+ # start/duration or a start/end? Maybe I can make it easier. Ideally, I
68
+ # would like to make it hard to encode an invalid Event.
69
+ # - I don't think its useful to have a default dtstart for events
70
+ # - also, I don't think dstart is mandatory
69
71
  dtstart = DirectoryInfo::Field.create('DTSTART', start)
70
72
  di = DirectoryInfo.create([ dtstart ], 'VEVENT')
71
73
 
@@ -114,50 +116,23 @@ module Vpim
114
116
  proptoken 'TRANSP', ["OPAQUE", "TRANSPARENT"], "OPAQUE"
115
117
  end
116
118
 
117
- # The duration in seconds of a Event, Todo, or Vfreebusy component, or
118
- # for Alarms, the delay period prior to repeating the alarm. The
119
- # duration is calculated from the DTEND and DTBEGIN fields if the
120
- # DURATION field is not present. Durations of zero seconds are possible.
119
+ # The duration in seconds of an Event, or nil if unspecified. If the
120
+ # DURATION field is not present, but the DTEND field is, the duration is
121
+ # calculated from DTSTART and DTEND. Durations of zero seconds are
122
+ # possible.
121
123
  def duration
122
- dur = @properties.field 'DURATION'
123
- dte = @properties.field 'DTEND'
124
- if !dur
125
- return nil unless dte
126
-
127
- b = dtstart
128
- e = dtend
129
-
130
- return (e - b).to_i
131
- end
132
-
133
- Icalendar.decode_duration(dur.value_raw)
124
+ propduration 'DTEND'
134
125
  end
135
126
 
136
- # The end time for this calendar component. For an Event, if there is no
137
- # end time, then nil is returned, and the event takes up no time.
138
- # However, the end time will be calculated from the event duration, if
139
- # present.
127
+ # The end time for this Event. If the DTEND field is not present, but the
128
+ # DURATION field is, the end will be calculated from DTSTART and
129
+ # DURATION.
140
130
  def dtend
141
- dte = @properties.field 'DTEND'
142
- if dte
143
- dte.to_time.first
144
- elsif duration
145
- dtstart + duration
146
- else
147
- nil
148
- end
131
+ propend 'DTEND'
149
132
  end
150
133
 
151
134
  # Make a new Vevent, or make changes to an existing Vevent.
152
135
  class Maker
153
- # TODO - should I automatically set
154
- # #created
155
- # #dtstamp
156
- # #sequence
157
- # ...?
158
- #
159
- # Many have pretty specific meanings in iTIP, perhaps I should leave
160
- # them alone.
161
136
  include Vpim::Icalendar::Set::Util #:nodoc:
162
137
  include Vpim::Icalendar::Set::Common
163
138
 
@@ -187,16 +162,23 @@ module Vpim
187
162
  set_date_or_datetime 'DTEND', 'DATE-TIME', dtend
188
163
  end
189
164
 
190
- # Yields a selector that allows the duration to be set.
191
- #
192
- # TODO - syntax is:
193
- # dur-value = (["+"] / "-") "P" (dur-date / dur-time / dur-week)
194
- # dur-date = dur-day [ "T" (dur-hour / dur-minute / dur-second) ]
195
- # dur-time = "T" (dur-hour / dur-minute / dur-second)
196
- # dur-week = 1*DIGIT "W"
197
- def duration(dur) #:yield:selector
198
- raise Vpim::Unsupported
165
+ # Add a RRULE to this event. The rule can be provided as a pre-build
166
+ # RRULE value, or the RRULE maker can be used.
167
+ def add_rrule(rule = nil, &block) #:yield: Rrule::Maker
168
+ # TODO - should be in Property::Reccurrence::Set
169
+ unless rule
170
+ rule = Rrule::Maker.new(&block).encode
171
+ end
172
+ @comp.properties.push(Vpim::DirectoryInfo::Field.create("RRULE", rule))
173
+ self
174
+ end
175
+
176
+ # Set the RRULE for this event. See #add_rrule
177
+ def set_rrule(rule = nil, &block) #:yield: Rrule::Maker
178
+ rm_all("RRULE")
179
+ add_rrule(rule, &block)
199
180
  end
181
+
200
182
  end
201
183
 
202
184
  end
@@ -0,0 +1,90 @@
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
+ module View
13
+
14
+ SECSPERDAY = 24 * 60 * 60
15
+
16
+ # View only events occuring in the next week.
17
+ module Week
18
+ def each(klass = nil) #:nodoc:
19
+ unless block_given?
20
+ return Enumerable::Enumerator.new(self, :each, klass)
21
+ end
22
+
23
+ t0 = Time.new.to_a
24
+ t0[0] = t0[1] = t0[2] = 0 # sec,min,hour = 0
25
+ t0 = Time.local(*t0)
26
+ t1 = t0 + 7 * SECSPERDAY
27
+
28
+ # Need to filter occurrences, too. Create modules for this on the fly.
29
+ occurrences = Module.new
30
+ # I'm passing state into the module's instance methods by doing string
31
+ # evaluation... which sucks, but I don't think I can get this closure in
32
+ # there.
33
+ occurrences.module_eval(<<"__", __FILE__, __LINE__+1)
34
+ def occurrences(dountil=nil)
35
+ unless block_given?
36
+ return Enumerable::Enumerator.new(self, :occurrences, dountil)
37
+ end
38
+ super(dountil) do |t|
39
+ t0 = Time.at(#{t0.to_i})
40
+ t1 = Time.at(#{t1.to_i})
41
+ break if t >= t1
42
+ tend = t
43
+ if respond_to? :duration
44
+ tend += duration || 0
45
+ end
46
+ if tend >= t0
47
+ yield t
48
+ end
49
+ end
50
+ end
51
+ __
52
+ =begin
53
+ block = lambda do |dountil|
54
+ unless block_given?
55
+ return Enumerable::Enumerator.new(self, :occurrences, dountil)
56
+ end
57
+ super(dountil) do |t|
58
+ break if t >= t1
59
+ yield t
60
+ end
61
+ end
62
+ occurrences.send(:define_method, :occurrences, block)
63
+ =end
64
+ super do |ve|
65
+ if ve.occurs_in?(t0, t1)
66
+ if ve.respond_to? :occurrences
67
+ ve.extend occurrences
68
+ end
69
+ yield ve
70
+ end
71
+ end
72
+ end
73
+ end
74
+
75
+ # Return a calendar view for the next week.
76
+ def self.week(cal)
77
+ cal.clone.extend Week.dup
78
+ end
79
+
80
+ module Todo
81
+ end
82
+
83
+ # Return a calendar view of only todos (optionally, include todos that
84
+ # are done).
85
+ def self.todos(cal, withdone=false)
86
+ end
87
+
88
+ end
89
+ end
90
+
@@ -30,6 +30,18 @@ module Vpim
30
30
  @elements = inner
31
31
  end
32
32
 
33
+ # TODO - derive everything from Icalendar::Component to get rid of this kind of stuff?
34
+ def fields #:nodoc:
35
+ f = properties.to_a
36
+ last = f.pop
37
+ f.push @elements
38
+ f.push last
39
+ end
40
+
41
+ def properties #:nodoc:
42
+ @properties
43
+ end
44
+
33
45
  # Create a Vjournal component.
34
46
  def self.create(fields=[])
35
47
  di = DirectoryInfo.create([], 'VJOURNAL')
@@ -9,7 +9,7 @@
9
9
  require 'vpim/version'
10
10
 
11
11
  #:main:README
12
- #:title:vpim - a library to manipulate vCards and iCalendars
12
+ #:title:vPim - vCard and iCalendar support for Ruby
13
13
  module Vpim
14
14
  # Exception used to indicate that data being decoded is invalid, the message
15
15
  # should describe what is invalid.
@@ -68,9 +68,18 @@ module Vpim
68
68
  new(di.to_a)
69
69
  end
70
70
 
71
- # The date and time that a to-do is expected to be completed, a Time.
71
+ # The duration in seconds of a Todo, or nil if unspecified. If the
72
+ # DURATION field is not present, but the DUE field is, the duration is
73
+ # calculated from DTSTART and DUE. Durations of zero seconds are
74
+ # possible.
75
+ def duration
76
+ propduration 'DUE'
77
+ end
78
+
79
+ # The time at which this Todo is due to be completed. If the DUE field is not present,
80
+ # but the DURATION field is, due will be calculated from DTSTART and DURATION.
72
81
  def due
73
- proptime 'DUE'
82
+ propend 'DUE'
74
83
  end
75
84
 
76
85
  # The date and time that a to-do was actually completed, a Time.