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.
@@ -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.