vpim 0.357 → 0.360
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/lib/vpim/address.rb +125 -87
- data/lib/vpim/icalendar.rb +37 -20
- data/lib/vpim/property/base.rb +80 -2
- data/lib/vpim/property/common.rb +137 -2
- data/lib/vpim/rfc2425.rb +37 -1
- data/lib/vpim/vcard.rb +6 -13
- data/lib/vpim/version.rb +2 -2
- data/lib/vpim/vevent.rb +73 -9
- metadata +5 -5
data/lib/vpim/address.rb
CHANGED
|
@@ -47,77 +47,31 @@ module Vpim
|
|
|
47
47
|
# a field.
|
|
48
48
|
#
|
|
49
49
|
# Example:
|
|
50
|
+
#
|
|
50
51
|
# ORGANIZER;CN="A. Person":mailto:a_person@example.com
|
|
52
|
+
#
|
|
51
53
|
# ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION
|
|
52
54
|
# ;CN="Sam Roberts";RSVP=TRUE:mailto:SRoberts@example.com
|
|
53
55
|
#
|
|
54
56
|
class Address
|
|
55
57
|
|
|
56
|
-
# Create an Address from a DirectoryInfo::Field, +field+.
|
|
57
|
-
#
|
|
58
|
-
# TODO - make private, and split into the encode/decode/create trinity.
|
|
59
|
-
def initialize(field)
|
|
60
|
-
unless field.value
|
|
61
|
-
raise ArgumentError
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
@field = field
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
# Return a representation of this Address as a DirectoryInfo::Field.
|
|
68
|
-
def field
|
|
69
|
-
@field.copy
|
|
70
|
-
end
|
|
71
|
-
|
|
72
58
|
# Create a copy of Address. If the original Address was frozen, this one
|
|
73
59
|
# won't be.
|
|
74
60
|
def copy
|
|
75
|
-
Marshal.load(Marshal.dump(self))
|
|
61
|
+
#Marshal.load(Marshal.dump(self))
|
|
62
|
+
self.dup.dirty
|
|
76
63
|
end
|
|
77
64
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
# Return true if the +uri+ is == to this address' URI. The comparison
|
|
84
|
-
# is case-insensitive.
|
|
85
|
-
#
|
|
86
|
-
# FIXME - why case insensitive? Email addresses. Should use a URI library
|
|
87
|
-
# if I can find one and it knows how to do URI comparisons.
|
|
88
|
-
def ==(uri)
|
|
89
|
-
Vpim::Methods.casecmp?(self.uri.to_str, uri.to_str)
|
|
90
|
-
end
|
|
91
|
-
|
|
92
|
-
# The common or displayable name associated with the calendar address,
|
|
93
|
-
# or nil if there is none.
|
|
94
|
-
def cn
|
|
95
|
-
return nil unless n = @field.param('CN')
|
|
96
|
-
|
|
97
|
-
# FIXME = the CN param may have no value, which is an error, but don't try
|
|
98
|
-
# to decode it, return either nil, or InvalidEncoding
|
|
99
|
-
Vpim.decode_text(n.first)
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
# A string representation of an address, using the common name, and the
|
|
103
|
-
# URI. The URI protocol is stripped if it's "mailto:".
|
|
104
|
-
#
|
|
105
|
-
# TODO - this needs to properly escape the cn string!
|
|
106
|
-
def to_s
|
|
107
|
-
u = uri
|
|
108
|
-
u.gsub!(/^mailto: */i, '')
|
|
109
|
-
|
|
110
|
-
if cn
|
|
111
|
-
"\"#{cn}\" <#{uri}>"
|
|
112
|
-
else
|
|
113
|
-
uri
|
|
114
|
-
end
|
|
115
|
-
end
|
|
116
|
-
|
|
117
|
-
def inspect
|
|
118
|
-
"#<Vpim::Icalendar::Address:cn=#{cn.inspect} status=#{partstat} rsvp?=#{rsvp} #{uri.inspect}>"
|
|
65
|
+
def dirty #:nodoc:
|
|
66
|
+
@field = nil
|
|
67
|
+
self
|
|
119
68
|
end
|
|
120
69
|
|
|
70
|
+
# Addresses in a CAL-ADDRESS are represented as a URI, usually a mailto URI.
|
|
71
|
+
attr_accessor :uri
|
|
72
|
+
# The common or displayable name associated with the calendar address, or
|
|
73
|
+
# nil if there is none.
|
|
74
|
+
attr_accessor :cn
|
|
121
75
|
# The participation role for the calendar user specified by the address.
|
|
122
76
|
#
|
|
123
77
|
# The standard roles are:
|
|
@@ -128,11 +82,7 @@ module Vpim
|
|
|
128
82
|
#
|
|
129
83
|
# The default role is REQ-PARTICIPANT, returned if no ROLE parameter was
|
|
130
84
|
# specified.
|
|
131
|
-
|
|
132
|
-
return 'REQ-PARTICIPANT' unless r = @field.param('ROLE')
|
|
133
|
-
r.first.upcase
|
|
134
|
-
end
|
|
135
|
-
|
|
85
|
+
attr_accessor :role
|
|
136
86
|
# The participation status for the calendar user specified by the
|
|
137
87
|
# property PARTSTAT, a String.
|
|
138
88
|
#
|
|
@@ -145,36 +95,124 @@ module Vpim
|
|
|
145
95
|
#
|
|
146
96
|
# Default is NEEDS-ACTION.
|
|
147
97
|
#
|
|
148
|
-
#
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
98
|
+
# FIXME - make the default depend on the component type.
|
|
99
|
+
attr_accessor :partstat
|
|
100
|
+
# The value of the RSVP field, either +true+ or +false+. It is used to
|
|
101
|
+
# specify whether there is an expectation of a reply from the calendar
|
|
102
|
+
# user specified by the property value.
|
|
103
|
+
attr_accessor :rsvp
|
|
104
|
+
|
|
105
|
+
def initialize(field=nil) #:nodoc:
|
|
106
|
+
@field = field
|
|
107
|
+
@uri = ''
|
|
108
|
+
@cn = ''
|
|
109
|
+
@role = "REQ-PARTICIPANT"
|
|
110
|
+
@partstat = "NEEDS-ACTION"
|
|
111
|
+
@rsvp = false
|
|
152
112
|
end
|
|
153
113
|
|
|
154
|
-
#
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
@field['PARTSTAT'] = status.to_str
|
|
160
|
-
status
|
|
114
|
+
# Create a new Address. It will encode as a +name+ property.
|
|
115
|
+
def self.create(uri='')
|
|
116
|
+
adr = new
|
|
117
|
+
adr.uri = uri.to_str
|
|
118
|
+
adr
|
|
161
119
|
end
|
|
162
120
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
121
|
+
def self.decode(field)
|
|
122
|
+
adr = new(field)
|
|
123
|
+
adr.uri = field.value
|
|
124
|
+
|
|
125
|
+
cn = field.param('CN')
|
|
126
|
+
|
|
127
|
+
if cn
|
|
128
|
+
adr.cn = cn.first
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
role = field.param('ROLE')
|
|
132
|
+
|
|
133
|
+
if role
|
|
134
|
+
adr.role = role.first.strip.upcase
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
partstat = field.param('PARTSTAT')
|
|
138
|
+
|
|
139
|
+
if partstat
|
|
140
|
+
adr.partstat = partstat.first.strip.upcase
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
rsvp = field.param('RSVP')
|
|
144
|
+
|
|
145
|
+
if rsvp
|
|
146
|
+
adr.rsvp = case rsvp.first
|
|
147
|
+
when /TRUE/i then true
|
|
148
|
+
when /FALSE/i then false
|
|
149
|
+
else raise InvalidEncodingError, "RSVP param value not TRUE/FALSE: #{rsvp}"
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
adr.freeze
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# Return a representation of this Address as a DirectoryInfo::Field.
|
|
157
|
+
def encode(name) #:nodoc:
|
|
158
|
+
if @field
|
|
159
|
+
# FIXME - set the field name, it could be different from cached
|
|
160
|
+
return @field
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
value = uri.to_str.strip
|
|
164
|
+
|
|
165
|
+
if value.empty?
|
|
166
|
+
raise Uencodeable, "Address#uri is zero-length"
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
params = {}
|
|
170
|
+
|
|
171
|
+
if cn.length > 0
|
|
172
|
+
params['CN'] = Vpim::encode_paramvalue(cn)
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# FIXME - default value is different for non-vEvent
|
|
176
|
+
if role.length > 0 && role != 'REQ-PARTICIPANT'
|
|
177
|
+
params['ROLE'] = Vpim::encode_paramtext(role)
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# FIXME - default value is different for non-vEvent
|
|
181
|
+
if partstat.length > 0 && partstat != 'NEEDS-ACTION'
|
|
182
|
+
params['PARTSTAT'] = Vpim::encode_paramtext(partstat)
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
if rsvp
|
|
186
|
+
params['RSVP'] = 'true'
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
Vpim::DirectoryInfo::Field.create(name, value, params)
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# Return true if the +uri+ is == to this address' URI. The comparison
|
|
193
|
+
# is case-insensitive (because email addresses and domain names are).
|
|
194
|
+
def ==(uri)
|
|
195
|
+
# TODO - could I use a URI library?
|
|
196
|
+
Vpim::Methods.casecmp?(self.uri.to_str, uri.to_str)
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# A string representation of an address, using the common name, and the
|
|
200
|
+
# URI. The URI protocol is stripped if it's "mailto:".
|
|
201
|
+
def to_s
|
|
202
|
+
u = uri
|
|
203
|
+
u = u.gsub(/^mailto: */i, '')
|
|
204
|
+
|
|
205
|
+
if cn.length > 0
|
|
206
|
+
"#{cn.inspect} <#{uri}>"
|
|
207
|
+
else
|
|
208
|
+
uri
|
|
176
209
|
end
|
|
177
210
|
end
|
|
211
|
+
|
|
212
|
+
def inspect #:nodoc:
|
|
213
|
+
"#<Vpim::Icalendar::Address:cn=#{cn.inspect} status=#{partstat} rsvp=#{rsvp} #{uri.inspect}>"
|
|
214
|
+
end
|
|
215
|
+
|
|
178
216
|
end
|
|
179
217
|
end
|
|
180
218
|
end
|
data/lib/vpim/icalendar.rb
CHANGED
|
@@ -33,10 +33,12 @@ module Vpim
|
|
|
33
33
|
# (iTIP) Scheduling Events, BusyTime, To-dos and Journal Entries
|
|
34
34
|
# - link:rfc2447.txt: iCalendar Message-Based Interoperability Protocol
|
|
35
35
|
#
|
|
36
|
-
# iCalendar
|
|
37
|
-
#
|
|
38
|
-
# VERSION:
|
|
39
|
-
#
|
|
36
|
+
# = iCalendar and vCalendar
|
|
37
|
+
#
|
|
38
|
+
# iCalendar files have VERSION:2.0 and vCalendar have VERSION:1.0. iCalendar
|
|
39
|
+
# (RFC 2445) is based on vCalendar, but but is not very compatible. While
|
|
40
|
+
# much appears to be similar, the recurrence rule syntax is completely
|
|
41
|
+
# different.
|
|
40
42
|
#
|
|
41
43
|
# iCalendars are usually transmitted in files with <code>.ics</code>
|
|
42
44
|
# extensions.
|
|
@@ -88,18 +90,37 @@ module Vpim
|
|
|
88
90
|
end
|
|
89
91
|
end
|
|
90
92
|
|
|
93
|
+
# Add and event to this calendar.
|
|
94
|
+
#
|
|
95
|
+
# Yields an event maker, Icalendar::Vevent::Maker.
|
|
96
|
+
def add_event(&block) #:yield:event
|
|
97
|
+
push Vevent::Maker.make( &block )
|
|
98
|
+
end
|
|
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)
|
|
105
|
+
# FIXME - make the primary API
|
|
106
|
+
di = DirectoryInfo.create( [ DirectoryInfo::Field.create('VERSION', '2.0') ], 'VCALENDAR' )
|
|
107
|
+
|
|
108
|
+
di.push_unique DirectoryInfo::Field.create('PRODID', Vpim::PRODID)
|
|
109
|
+
di.push_unique DirectoryInfo::Field.create('CALSCALE', "Gregorian")
|
|
110
|
+
|
|
111
|
+
new(di.to_a)
|
|
112
|
+
end
|
|
113
|
+
|
|
91
114
|
# Create a new Icalendar object with the minimal set of fields for a valid
|
|
92
115
|
# Calendar. If specified, +fields+ must be an array of
|
|
93
116
|
# DirectoryInfo::Field objects to add. They can override the the default
|
|
94
117
|
# Calendar fields, so, for example, this can be used to set a custom PRODID field.
|
|
95
|
-
#
|
|
96
|
-
# TODO - allow hash args like Vevent.create
|
|
97
118
|
def Icalendar.create(fields=[])
|
|
98
119
|
di = DirectoryInfo.create( [ DirectoryInfo::Field.create('VERSION', '2.0') ], 'VCALENDAR' )
|
|
99
120
|
|
|
100
121
|
DirectoryInfo::Field.create_array(fields).each { |f| di.push_unique f }
|
|
101
122
|
|
|
102
|
-
di.push_unique DirectoryInfo::Field.create('PRODID',
|
|
123
|
+
di.push_unique DirectoryInfo::Field.create('PRODID', Vpim::PRODID)
|
|
103
124
|
di.push_unique DirectoryInfo::Field.create('CALSCALE', "Gregorian")
|
|
104
125
|
|
|
105
126
|
new(di.to_a)
|
|
@@ -124,10 +145,16 @@ module Vpim
|
|
|
124
145
|
Icalendar.create(fields)
|
|
125
146
|
end
|
|
126
147
|
|
|
148
|
+
# Used during encoding.
|
|
149
|
+
def fields # :nodoc:
|
|
150
|
+
f = @properties.to_a
|
|
151
|
+
last = f.pop
|
|
152
|
+
@components.each { |c| f << c.fields }
|
|
153
|
+
f.push last
|
|
154
|
+
end
|
|
155
|
+
|
|
127
156
|
# Encode the Calendar as a string. The width is the maximum width of the
|
|
128
157
|
# encoded lines, it can be specified, but is better left to the default.
|
|
129
|
-
#
|
|
130
|
-
# TODO - only does top-level now, needs to add the events/todos/etc.
|
|
131
158
|
def encode(width=nil)
|
|
132
159
|
# We concatenate the fields of all objects, create a DirInfo, then
|
|
133
160
|
# encode it.
|
|
@@ -135,17 +162,6 @@ module Vpim
|
|
|
135
162
|
di.encode(width)
|
|
136
163
|
end
|
|
137
164
|
|
|
138
|
-
# Used during encoding.
|
|
139
|
-
def fields # :nodoc:
|
|
140
|
-
fields = @properties.to_a
|
|
141
|
-
|
|
142
|
-
last = fields.pop
|
|
143
|
-
|
|
144
|
-
@components.each { |c| fields << c.fields }
|
|
145
|
-
|
|
146
|
-
fields << last
|
|
147
|
-
end
|
|
148
|
-
|
|
149
165
|
alias to_s encode
|
|
150
166
|
|
|
151
167
|
# Push a calendar component onto the calendar.
|
|
@@ -156,6 +172,7 @@ module Vpim
|
|
|
156
172
|
else
|
|
157
173
|
raise ArgumentError, "can't add a #{component.type} to a calendar"
|
|
158
174
|
end
|
|
175
|
+
self
|
|
159
176
|
end
|
|
160
177
|
|
|
161
178
|
# Check if the protocol method is +method+
|
data/lib/vpim/property/base.rb
CHANGED
|
@@ -8,10 +8,88 @@
|
|
|
8
8
|
|
|
9
9
|
module Vpim
|
|
10
10
|
class Icalendar
|
|
11
|
+
module Set #:nodoc:
|
|
12
|
+
module Util #:nodoc:
|
|
13
|
+
|
|
14
|
+
def rm_all(name)
|
|
15
|
+
rm = @comp.properties.select { |f| f.name? name }
|
|
16
|
+
rm.each { |f| @comp.properties.delete(f) }
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def set_token(name, allowed, default, value) #:nodoc:
|
|
20
|
+
value = value.to_str
|
|
21
|
+
unless allowed.include?(value)
|
|
22
|
+
raise Vpim::Unencodeable, "Invalid #{name} value '#{value}'"
|
|
23
|
+
end
|
|
24
|
+
rm_all(name)
|
|
25
|
+
unless value == default
|
|
26
|
+
@comp.properties.push Vpim::DirectoryInfo::Field.create(name, value)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def field_create(name, value, default_value_type = nil, value_type = nil, params = {})
|
|
31
|
+
if value_type && value_type != default_value_type
|
|
32
|
+
params['VALUE'] = value_type
|
|
33
|
+
end
|
|
34
|
+
Vpim::DirectoryInfo::Field.create(name, value, params)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def set_date_or_datetime(name, default, value)
|
|
38
|
+
f = nil
|
|
39
|
+
case value
|
|
40
|
+
when Date
|
|
41
|
+
f = field_create(name, Vpim.encode_date(value), default, 'DATE')
|
|
42
|
+
when Time
|
|
43
|
+
f = field_create(name, Vpim.encode_date_time(value), default, 'DATE-TIME')
|
|
44
|
+
else
|
|
45
|
+
raise Vpim::Unencodeable, "Invalid #{name} value #{value.inspect}"
|
|
46
|
+
end
|
|
47
|
+
rm_all(name)
|
|
48
|
+
@comp.properties.push(f)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def set_datetime(name, value)
|
|
52
|
+
f = field_create(name, Vpim.encode_date_time(value))
|
|
53
|
+
rm_all(name)
|
|
54
|
+
@comp.properties.push(f)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def set_text(name, value)
|
|
58
|
+
f = field_create(name, Vpim.encode_text(value))
|
|
59
|
+
rm_all(name)
|
|
60
|
+
@comp.properties.push(f)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def set_text_list(name, value)
|
|
64
|
+
f = field_create(name, Vpim.encode_text_list(value))
|
|
65
|
+
rm_all(name)
|
|
66
|
+
@comp.properties.push(f)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def set_integer(name, value)
|
|
70
|
+
value = value.to_int.to_s
|
|
71
|
+
f = field_create(name, value)
|
|
72
|
+
rm_all(name)
|
|
73
|
+
@comp.properties.push(f)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def add_address(name, value)
|
|
77
|
+
f = value.encode(name)
|
|
78
|
+
@comp.properties.push(f)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def set_address(name, value)
|
|
82
|
+
rm_all(name)
|
|
83
|
+
add_address(name, value)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
11
89
|
module Property #:nodoc:
|
|
12
90
|
|
|
13
|
-
# FIXME -
|
|
14
|
-
module Base
|
|
91
|
+
# FIXME - rename Base to Util
|
|
92
|
+
module Base #:nodoc:
|
|
15
93
|
# Value of first property with name +name+
|
|
16
94
|
def propvalue(name) #:nodoc:
|
|
17
95
|
prop = @properties.detect { |f| f.name? name }
|
data/lib/vpim/property/common.rb
CHANGED
|
@@ -78,7 +78,7 @@ module Vpim
|
|
|
78
78
|
organizer = @properties.field('ORGANIZER')
|
|
79
79
|
|
|
80
80
|
if organizer
|
|
81
|
-
organizer = Icalendar::Address.
|
|
81
|
+
organizer = Icalendar::Address.decode(organizer)
|
|
82
82
|
end
|
|
83
83
|
|
|
84
84
|
organizer.freeze
|
|
@@ -128,7 +128,7 @@ seq
|
|
|
128
128
|
# attendees are objects of Icalendar::Address. If +uri+ is specified
|
|
129
129
|
# only the return the attendees with this +uri+.
|
|
130
130
|
def attendees(uri = nil)
|
|
131
|
-
attendees = @properties.enum_by_name('ATTENDEE').map { |a| Icalendar::Address.
|
|
131
|
+
attendees = @properties.enum_by_name('ATTENDEE').map { |a| Icalendar::Address.decode(a) }
|
|
132
132
|
attendees.freeze
|
|
133
133
|
if uri
|
|
134
134
|
attendees.select { |a| a == uri }
|
|
@@ -174,6 +174,141 @@ seq
|
|
|
174
174
|
end
|
|
175
175
|
|
|
176
176
|
end
|
|
177
|
+
|
|
178
|
+
module Set
|
|
179
|
+
|
|
180
|
+
# Properties common to Vevent, Vtodo, and Vjournal.
|
|
181
|
+
module Common
|
|
182
|
+
|
|
183
|
+
# Set the access class of the component, see Icalendar::Get::Common#access_class.
|
|
184
|
+
def access_class(token)
|
|
185
|
+
set_token 'CLASS', ["PUBLIC", "PRIVATE", "CONFIDENTIAL"], "PUBLIC", token
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# Set the creation time, see Icalendar::Get::Common#created
|
|
189
|
+
def created(time)
|
|
190
|
+
set_datetime 'CREATED', time
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
# Set the description, see Icalendar::Get::Common#description.
|
|
194
|
+
def description(text)
|
|
195
|
+
set_text 'DESCRIPTION', text
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# Set the sequence number, see Icalendar::Get::Common#sequence.
|
|
199
|
+
# is no SEQUENCE; property.
|
|
200
|
+
def sequence(int)
|
|
201
|
+
set_integer 'SEQUENCE', int
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
# Set the timestamp, see Icalendar::Get::Common#timestamp.
|
|
205
|
+
def dtstamp(time)
|
|
206
|
+
set_datetime 'DTSTAMP', time
|
|
207
|
+
self
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
# The start time or date, see Icalendar::Get::Common#dtstart.
|
|
211
|
+
def dtstart(start)
|
|
212
|
+
set_date_or_datetime 'DTSTART', 'DATE-TIME', start
|
|
213
|
+
self
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
# Set the last modification time, see Icalendar::Get::Common#lastmod.
|
|
217
|
+
def lastmod(time)
|
|
218
|
+
set_datetime 'LAST-MODIFIED', time
|
|
219
|
+
self
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
# Set the event organizer, an Icalendar::Address, see Icalendar::Get::Common#organizer.
|
|
223
|
+
#
|
|
224
|
+
# Without an +adr+ it yields an Icalendar::Address that is a copy of
|
|
225
|
+
# the current organizer (if any), allowing it to be modified.
|
|
226
|
+
def organizer(adr=nil) #:yield: organizer
|
|
227
|
+
unless adr
|
|
228
|
+
adr = @comp.organizer
|
|
229
|
+
if adr
|
|
230
|
+
adr = adr.copy
|
|
231
|
+
else
|
|
232
|
+
adr = Icalendar::Address.create
|
|
233
|
+
end
|
|
234
|
+
yield adr
|
|
235
|
+
end
|
|
236
|
+
set_address('ORGANIZER', adr)
|
|
237
|
+
self
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
=begin
|
|
241
|
+
# Status values are not rejected during decoding. However, if the
|
|
242
|
+
# status is requested, and it's value is not one of the defined
|
|
243
|
+
# allowable values, an exception is raised.
|
|
244
|
+
def status
|
|
245
|
+
case self
|
|
246
|
+
when Vpim::Icalendar::Vevent
|
|
247
|
+
proptoken 'STATUS', ['TENTATIVE', 'CONFIRMED', 'CANCELLED']
|
|
248
|
+
|
|
249
|
+
when Vpim::Icalendar::Vtodo
|
|
250
|
+
proptoken 'STATUS', ['NEEDS-ACTION', 'COMPLETED', 'IN-PROCESS', 'CANCELLED']
|
|
251
|
+
|
|
252
|
+
when Vpim::Icalendar::Vevent
|
|
253
|
+
proptoken 'STATUS', ['DRAFT', 'FINAL', 'CANCELLED']
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
=end
|
|
257
|
+
|
|
258
|
+
# Set summary description of component, see Icalendar::Get::Common#summary.
|
|
259
|
+
def summary(text)
|
|
260
|
+
set_text 'SUMMARY', text
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
# Set the unique identifier of this calendar component, see Icalendar::Get::Common#uid.
|
|
264
|
+
def uid(uid)
|
|
265
|
+
set_text 'UID', uid
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
def url(url)
|
|
269
|
+
set_text 'URL', url
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
# Add an attendee Address, see Icalendar::Get::Common#attendees.
|
|
273
|
+
def add_attendee(adr)
|
|
274
|
+
add_address('ATTENDEE', adr)
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
# Set the categories, see Icalendar::Get::Common#attendees.
|
|
278
|
+
#
|
|
279
|
+
# If +cats+ is provided, the categories are set to cats, either a
|
|
280
|
+
# String or an Array of String. Otherwise, and array of the existing
|
|
281
|
+
# category strings is yielded, and it can be modified.
|
|
282
|
+
def categories(cats = nil) #:yield: categories
|
|
283
|
+
unless cats
|
|
284
|
+
cats = @comp.categories
|
|
285
|
+
yield cats
|
|
286
|
+
end
|
|
287
|
+
# TODO - strip the strings
|
|
288
|
+
set_text_list('CATEGORIES', cats)
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
# Set the comment, see Icalendar::Get::Common#comment.
|
|
292
|
+
def comment(value)
|
|
293
|
+
set_text 'COMMENT', value
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
=begin
|
|
297
|
+
def contacts
|
|
298
|
+
proptextarray 'CONTACT'
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
# An Array of attachments, see Attachment for more information.
|
|
302
|
+
def attachments
|
|
303
|
+
@properties.enum_by_name('ATTACH').map do |f|
|
|
304
|
+
attachment = Attachment.decode(f, 'uri', 'FMTTYPE')
|
|
305
|
+
end
|
|
306
|
+
end
|
|
307
|
+
=end
|
|
308
|
+
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
end
|
|
177
312
|
end
|
|
178
313
|
end
|
|
179
314
|
|
data/lib/vpim/rfc2425.rb
CHANGED
|
@@ -56,6 +56,14 @@ module Vpim
|
|
|
56
56
|
|
|
57
57
|
# integer = (["+"] / "-") 1*DIGIT
|
|
58
58
|
INTEGER = '[-+]?\d+'
|
|
59
|
+
|
|
60
|
+
# QSAFE-CHAR = WSP / %x21 / %x23-7E / NON-US-ASCII
|
|
61
|
+
# ; Any character except CTLs and DQUOTE
|
|
62
|
+
QSAFECHAR = '[ \t\x21\x23-\x7e\x80-\xff]'
|
|
63
|
+
|
|
64
|
+
# SAFE-CHAR = WSP / %x21 / %x23-2B / %x2D-39 / %x3C-7E / NON-US-ASCII
|
|
65
|
+
# ; Any character except CTLs, DQUOTE, ";", ":", ","
|
|
66
|
+
SAFECHAR = '[ \t\x21\x23-\x2b\x2d-\x39\x3c-\x7e\x80-\xff]'
|
|
59
67
|
end
|
|
60
68
|
end
|
|
61
69
|
|
|
@@ -232,8 +240,13 @@ module Vpim
|
|
|
232
240
|
end
|
|
233
241
|
end
|
|
234
242
|
|
|
243
|
+
# v is an Array of String, or just a single String
|
|
235
244
|
def Vpim.encode_text_list(v, sep = ",") #:nodoc:
|
|
236
|
-
|
|
245
|
+
begin
|
|
246
|
+
v.to_ary.map{ |t| Vpim.encode_text(t) }.join(sep)
|
|
247
|
+
rescue
|
|
248
|
+
Vpim.encode_text(v)
|
|
249
|
+
end
|
|
237
250
|
end
|
|
238
251
|
|
|
239
252
|
# Convert a +sep+-seperated list of TEXT values into an array of values.
|
|
@@ -248,6 +261,29 @@ module Vpim
|
|
|
248
261
|
list
|
|
249
262
|
end
|
|
250
263
|
|
|
264
|
+
# param-value = paramtext / quoted-string
|
|
265
|
+
# paramtext = *SAFE-CHAR
|
|
266
|
+
# quoted-string = DQUOTE *QSAFE-CHAR DQUOTE
|
|
267
|
+
def Vpim.encode_paramtext(value)
|
|
268
|
+
case value
|
|
269
|
+
when %r{\A#{Bnf::SAFECHAR}*\z}
|
|
270
|
+
value
|
|
271
|
+
else
|
|
272
|
+
raise Vpim::Unencodable, "paramtext #{value.inspect}"
|
|
273
|
+
end
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
def Vpim.encode_paramvalue(value)
|
|
277
|
+
case value
|
|
278
|
+
when %r{\A#{Bnf::SAFECHAR}*\z}
|
|
279
|
+
value
|
|
280
|
+
when %r{\A#{Bnf::QSAFECHAR}*\z}
|
|
281
|
+
'"' + value + '"'
|
|
282
|
+
else
|
|
283
|
+
raise Vpim::Unencodable, "param-value #{value.inspect}"
|
|
284
|
+
end
|
|
285
|
+
end
|
|
286
|
+
|
|
251
287
|
|
|
252
288
|
# Unfold the lines in +card+, then return an array of one Field object per
|
|
253
289
|
# line.
|
data/lib/vpim/vcard.rb
CHANGED
|
@@ -1078,19 +1078,12 @@ module Vpim
|
|
|
1078
1078
|
|
|
1079
1079
|
public
|
|
1080
1080
|
|
|
1081
|
-
#
|
|
1082
|
-
#
|
|
1083
|
-
#
|
|
1084
|
-
#
|
|
1085
|
-
#
|
|
1086
|
-
#
|
|
1087
|
-
# 'N',
|
|
1088
|
-
# x.map { |s| s ? s.to_str : '' }
|
|
1089
|
-
# )
|
|
1090
|
-
# self
|
|
1091
|
-
# end
|
|
1092
|
-
# Set with #name now.
|
|
1093
|
-
# Use m.name do |n| n.fullname = foo end
|
|
1081
|
+
# Deprecated, see #name.
|
|
1082
|
+
#
|
|
1083
|
+
# Use
|
|
1084
|
+
# maker.name do |n| n.fullname = "foo" end
|
|
1085
|
+
# to set just fullname, or set the other fields to set fullname and the
|
|
1086
|
+
# name.
|
|
1094
1087
|
def fullname=(fullname) #:nodoc: bacwards compat
|
|
1095
1088
|
if @card.field('FN')
|
|
1096
1089
|
raise Vpim::InvalidEncodingError, "Not allowed to add more than one FN field to a vCard."
|
data/lib/vpim/version.rb
CHANGED
data/lib/vpim/vevent.rb
CHANGED
|
@@ -20,6 +20,7 @@ require 'vpim/property/recurrence'
|
|
|
20
20
|
module Vpim
|
|
21
21
|
class Icalendar
|
|
22
22
|
class Vevent
|
|
23
|
+
|
|
23
24
|
include Vpim::Icalendar::Property::Base
|
|
24
25
|
include Vpim::Icalendar::Property::Common
|
|
25
26
|
include Vpim::Icalendar::Property::Priority
|
|
@@ -37,6 +38,19 @@ module Vpim
|
|
|
37
38
|
# See "TODO - fields" in dirinfo.rb
|
|
38
39
|
end
|
|
39
40
|
|
|
41
|
+
# TODO - derive everything from Icalendar::Component to get this kind of stuff?
|
|
42
|
+
def fields #:nodoc:
|
|
43
|
+
f = @properties.to_a
|
|
44
|
+
last = f.pop
|
|
45
|
+
f.push @elements
|
|
46
|
+
f.push last
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def properties #:nodoc:
|
|
50
|
+
@properties
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
|
|
40
54
|
# Create a new Vevent object. All events must have a DTSTART field,
|
|
41
55
|
# specify it as either a Time or a Date in +start+, it defaults to "now"
|
|
42
56
|
# (is this useful?).
|
|
@@ -71,10 +85,12 @@ module Vpim
|
|
|
71
85
|
|
|
72
86
|
# Accept an event invitation. The +invitee+ is the Address that wishes
|
|
73
87
|
# to accept the event invitation as confirmed.
|
|
88
|
+
#
|
|
89
|
+
# The event created is identical to this one, but
|
|
90
|
+
# - without the attendees
|
|
91
|
+
# - with the invitee added with a PARTSTAT of ACCEPTED
|
|
74
92
|
def accept(invitee)
|
|
75
|
-
#
|
|
76
|
-
# - without the attendees
|
|
77
|
-
# - with the invitee added with a PARTSTAT of ACCEPTED
|
|
93
|
+
# FIXME - move to Vpim::Itip.
|
|
78
94
|
invitee = invitee.copy
|
|
79
95
|
invitee.partstat = 'ACCEPTED'
|
|
80
96
|
|
|
@@ -84,7 +100,7 @@ module Vpim
|
|
|
84
100
|
|f,i|
|
|
85
101
|
|
|
86
102
|
# put invitee in as field[1]
|
|
87
|
-
fields << invitee.
|
|
103
|
+
fields << invitee.encode('ATTENDEE') if i == 1
|
|
88
104
|
|
|
89
105
|
fields << f unless f.name? 'ATTENDEE'
|
|
90
106
|
end
|
|
@@ -92,11 +108,8 @@ module Vpim
|
|
|
92
108
|
Vevent.new(fields)
|
|
93
109
|
end
|
|
94
110
|
|
|
95
|
-
|
|
96
|
-
#
|
|
97
|
-
# TODO - def dtstart=(start) ... start should be allowed to be Time/Date/DateTime
|
|
98
|
-
=end
|
|
99
|
-
|
|
111
|
+
# In iTIP, whether this event is OPAQUE or TRANSPARENT to scheduling. If
|
|
112
|
+
# transparency is not explicitly set, it defaults to OPAQUE.
|
|
100
113
|
def transparency
|
|
101
114
|
proptoken 'TRANSP', ["OPAQUE", "TRANSPARENT"], "OPAQUE"
|
|
102
115
|
end
|
|
@@ -135,6 +148,57 @@ module Vpim
|
|
|
135
148
|
end
|
|
136
149
|
end
|
|
137
150
|
|
|
151
|
+
# Make a new Vevent, or make changes to an existing Vevent.
|
|
152
|
+
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
|
+
include Vpim::Icalendar::Set::Util #:nodoc:
|
|
162
|
+
include Vpim::Icalendar::Set::Common
|
|
163
|
+
|
|
164
|
+
# The event that changes are being made to.
|
|
165
|
+
attr_reader :event
|
|
166
|
+
|
|
167
|
+
def initialize(event) #:nodoc:
|
|
168
|
+
@event = event
|
|
169
|
+
@comp = event
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# Make changes to +event+. If +event+ is not specified, creates a new
|
|
173
|
+
# event. Yields a Vevent::Maker, and returns +event+.
|
|
174
|
+
def self.make(event = Vpim::Icalendar::Vevent.create) #:yield:maker
|
|
175
|
+
m = self.new(event)
|
|
176
|
+
yield m
|
|
177
|
+
m.event
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# Set transparency to "OPAQUE" or "TRANSPARENT", see Vpim::Vevent#transparency.
|
|
181
|
+
def transparency(token)
|
|
182
|
+
set_token 'TRANSP', ["OPAQUE", "TRANSPARENT"], "OPAQUE", token
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# Set end for events with fixed durations. +end+ can be a Date or Time
|
|
186
|
+
def dtend(dtend)
|
|
187
|
+
set_date_or_datetime 'DTEND', 'DATE-TIME', dtend
|
|
188
|
+
end
|
|
189
|
+
|
|
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
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
|
|
138
202
|
end
|
|
139
203
|
end
|
|
140
204
|
end
|
metadata
CHANGED
|
@@ -3,15 +3,15 @@ rubygems_version: 0.8.11
|
|
|
3
3
|
specification_version: 1
|
|
4
4
|
name: vpim
|
|
5
5
|
version: !ruby/object:Gem::Version
|
|
6
|
-
version: "0.
|
|
7
|
-
date: 2006-
|
|
8
|
-
summary:
|
|
6
|
+
version: "0.360"
|
|
7
|
+
date: 2006-04-02 00:00:00 -08:00
|
|
8
|
+
summary: iCalendar and vCard support for ruby
|
|
9
9
|
require_paths:
|
|
10
10
|
- lib
|
|
11
11
|
email: sroberts@uniserve.com
|
|
12
12
|
homepage: http://vpim.rubyforge.org
|
|
13
|
-
rubyforge_project:
|
|
14
|
-
description:
|
|
13
|
+
rubyforge_project: vpim
|
|
14
|
+
description: This is a pure-ruby library for decoding and encoding vCard and iCalendar data ("personal information") called vPim.
|
|
15
15
|
autorequire: vpim
|
|
16
16
|
default_executable:
|
|
17
17
|
bindir: bin
|