vpim 0.619 → 0.658
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES +60 -0
- data/README +37 -30
- data/bin/reminder +1 -1
- data/lib/atom.rb +728 -0
- data/lib/atom/pub.rb +206 -0
- data/lib/atom/version.rb +9 -0
- data/lib/atom/xml/parser.rb +305 -0
- data/lib/plist.rb +22 -0
- data/lib/plist/generator.rb +224 -0
- data/lib/plist/parser.rb +225 -0
- data/lib/vpim/date.rb +1 -1
- data/lib/vpim/enumerator.rb +6 -1
- data/lib/vpim/field.rb +1 -0
- data/lib/vpim/icalendar.rb +74 -29
- data/lib/vpim/property/base.rb +31 -1
- data/lib/vpim/property/common.rb +13 -13
- data/lib/vpim/property/recurrence.rb +30 -14
- data/lib/vpim/repo.rb +120 -55
- data/lib/vpim/rfc2425.rb +5 -4
- data/lib/vpim/rrule.rb +70 -7
- data/lib/vpim/vcard.rb +26 -2
- data/lib/vpim/version.rb +2 -2
- data/lib/vpim/vevent.rb +33 -51
- data/lib/vpim/view.rb +90 -0
- data/lib/vpim/vjournal.rb +12 -0
- data/lib/vpim/vpim.rb +1 -1
- data/lib/vpim/vtodo.rb +11 -2
- data/samples/reminder.rb +1 -1
- data/test/test_all.rb +10 -7
- data/test/test_date.rb +2 -2
- data/test/test_ical.rb +199 -4
- data/test/test_repo.rb +158 -0
- data/test/test_rrule.rb +51 -0
- data/test/test_vcard.rb +129 -0
- data/test/test_view.rb +79 -0
- metadata +16 -12
data/lib/vpim/rfc2425.rb
CHANGED
@@ -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
|
18
|
-
# Note: added '/' to allowed because its produced by KAddressBook
|
19
|
-
|
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
|
-
#
|
352
|
+
# TODO - use Enumerable#partition
|
352
353
|
# seperate into the outer-level fields, and the arrays of component
|
353
354
|
# fields
|
354
355
|
outer = []
|
data/lib/vpim/rrule.rb
CHANGED
@@ -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
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
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
|
-
|
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
|
|
data/lib/vpim/vcard.rb
CHANGED
@@ -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
|
-
|
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 );
|
data/lib/vpim/version.rb
CHANGED
data/lib/vpim/vevent.rb
CHANGED
@@ -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
|
118
|
-
#
|
119
|
-
#
|
120
|
-
#
|
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
|
-
|
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
|
137
|
-
#
|
138
|
-
#
|
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
|
-
|
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
|
-
#
|
191
|
-
#
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
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
|
data/lib/vpim/view.rb
ADDED
@@ -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
|
+
|
data/lib/vpim/vjournal.rb
CHANGED
@@ -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')
|
data/lib/vpim/vpim.rb
CHANGED
@@ -9,7 +9,7 @@
|
|
9
9
|
require 'vpim/version'
|
10
10
|
|
11
11
|
#:main:README
|
12
|
-
#:title:
|
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.
|
data/lib/vpim/vtodo.rb
CHANGED
@@ -68,9 +68,18 @@ module Vpim
|
|
68
68
|
new(di.to_a)
|
69
69
|
end
|
70
70
|
|
71
|
-
# The
|
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
|
-
|
82
|
+
propend 'DUE'
|
74
83
|
end
|
75
84
|
|
76
85
|
# The date and time that a to-do was actually completed, a Time.
|