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