icalendar 1.5.0 → 1.5.1
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.
- checksums.yaml +4 -4
- data/History.txt +4 -0
- data/README.md +44 -1
- data/Rakefile +8 -0
- data/lib/icalendar/base.rb +1 -1
- data/lib/icalendar/calendar.rb +35 -52
- data/lib/icalendar/component.rb +63 -82
- data/lib/icalendar/component/event.rb +1 -1
- data/lib/icalendar/component/freebusy.rb +1 -1
- data/lib/icalendar/component/journal.rb +1 -1
- data/lib/icalendar/component/timezone.rb +0 -13
- data/lib/icalendar/component/todo.rb +1 -1
- data/lib/icalendar/parser.rb +13 -0
- data/test/test_calendar.rb +44 -1
- data/test/test_parameter.rb +4 -2
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 44c08a1f244a35efd4affc4705028dfe9798f707
|
4
|
+
data.tar.gz: f67a4772936897ed6fed5efd4d3d6d8f6c564ac9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 336fc24e516f61e887be407e55c5670475c965770a13e42be3b97fc1843cb02f5a21f40cba4aa76887f868c51637c0d9510548748f4dce34d8ff0d62579d0529
|
7
|
+
data.tar.gz: aeb52da8e30cea70b4faaa2b931b827e19952837f85c2403959859702fa2b80213a0ef4d05d0c7d45ac0ab99bc90b7fa6f96e36e5bc7d1c163d1f6ac4dca06ee
|
data/History.txt
CHANGED
data/README.md
CHANGED
@@ -6,6 +6,24 @@ iCalendar -- Internet calendaring, Ruby style
|
|
6
6
|
|
7
7
|
<http://github.com/icalendar/icalendar>
|
8
8
|
|
9
|
+
2.x Status
|
10
|
+
---
|
11
|
+
|
12
|
+
iCalendar 2.0 is under active development, and can be followed in the
|
13
|
+
[2.0beta branch](https://github.com/icalendar/icalendar/tree/2.0beta).
|
14
|
+
|
15
|
+
iCalendar 1.x (currently the master branch) will still survive for a
|
16
|
+
while, but will only be accepting bug fixes from this point forward
|
17
|
+
unless someone else wants to take over more active maintainership of
|
18
|
+
the 1.x series.
|
19
|
+
|
20
|
+
### 2.0 Goals ###
|
21
|
+
|
22
|
+
* Implements [RFC 5545](http://tools.ietf.org/html/rfc5545)
|
23
|
+
* More obvious access to parameters and values
|
24
|
+
* Cleaner & easier timezone support
|
25
|
+
|
26
|
+
|
9
27
|
DESCRIPTION
|
10
28
|
---
|
11
29
|
|
@@ -92,7 +110,7 @@ ALARMS
|
|
92
110
|
|
93
111
|
### Within an event ###
|
94
112
|
|
95
|
-
cal.event
|
113
|
+
cal.event do
|
96
114
|
# ...other event properties
|
97
115
|
alarm do
|
98
116
|
action "EMAIL"
|
@@ -186,6 +204,31 @@ TIMEZONES
|
|
186
204
|
# END:STANDARD
|
187
205
|
# END:VTIMEZONE
|
188
206
|
|
207
|
+
iCalendar has some basic support for creating VTIMEZONE blocks from timezone information pulled from `tzinfo`. You must require `tzinfo` support manually to take advantage, and iCalendar only supports `tzinfo` with versions `~> 0.3`
|
208
|
+
|
209
|
+
#### Example ####
|
210
|
+
|
211
|
+
require 'tzinfo'
|
212
|
+
require 'icalendar/tzinfo'
|
213
|
+
|
214
|
+
cal = Calendar.new
|
215
|
+
|
216
|
+
event_start = DateTime.new 2008, 12, 29, 8, 0, 0
|
217
|
+
event_end = DateTime.new 2008, 12, 29, 11, 0, 0
|
218
|
+
|
219
|
+
tzid = "America/Chicago"
|
220
|
+
tz = TZInfo::Timezone.get tzid
|
221
|
+
timezone = tz.ical_timezone event_start
|
222
|
+
cal.add timezone
|
223
|
+
|
224
|
+
cal.event do
|
225
|
+
dtstart event_start.tap { |d| d.ical_params = {'TZID' => tzid} }
|
226
|
+
dtend event_end.tap { |d| d.ical_params = {'TZID' => tzid} }
|
227
|
+
summary "Meeting with the man."
|
228
|
+
description "Have a long lunch meeting and decide nothing..."
|
229
|
+
end
|
230
|
+
|
231
|
+
|
189
232
|
Unicode
|
190
233
|
---
|
191
234
|
|
data/Rakefile
CHANGED
data/lib/icalendar/base.rb
CHANGED
data/lib/icalendar/calendar.rb
CHANGED
@@ -25,88 +25,60 @@ module Icalendar
|
|
25
25
|
self.version = "2.0" # Version of the specification
|
26
26
|
end
|
27
27
|
|
28
|
-
def
|
29
|
-
"
|
30
|
-
|
31
|
-
|
32
|
-
# Then the properties
|
33
|
-
print_properties(@properties.select { |k,v| k != 'version' }) +
|
34
|
-
|
35
|
-
# sub components
|
36
|
-
yield +
|
28
|
+
def print_headers
|
29
|
+
"VERSION:#{version}\r\n"
|
30
|
+
end
|
37
31
|
|
38
|
-
|
39
|
-
|
32
|
+
def properties_to_print
|
33
|
+
@properties.select { |k,v| k != 'version' }
|
40
34
|
end
|
41
35
|
|
42
36
|
def event(&block)
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
37
|
+
calendar_tzid = timezone_id
|
38
|
+
build_component Event.new do
|
39
|
+
# Note: I'm not sure this is the best way to pass this down, but it works
|
40
|
+
self.tzid = calendar_tzid
|
41
|
+
|
42
|
+
if block
|
43
|
+
instance_eval(&block)
|
44
|
+
if tzid
|
45
|
+
dtstart.ical_params = { "TZID" => e.tzid }
|
46
|
+
dtend.ical_params = { "TZID" => e.tzid } unless dtend.nil?
|
47
|
+
end
|
54
48
|
end
|
55
49
|
end
|
56
|
-
|
57
|
-
e
|
58
50
|
end
|
59
51
|
|
60
52
|
def find_event(uid)
|
61
|
-
|
53
|
+
events.find {|e| e.uid == uid}
|
62
54
|
end
|
63
55
|
|
64
56
|
def todo(&block)
|
65
|
-
|
66
|
-
self.add_component e
|
67
|
-
|
68
|
-
e.instance_eval(&block) if block
|
69
|
-
|
70
|
-
e
|
57
|
+
build_component Todo.new, &block
|
71
58
|
end
|
72
59
|
|
73
60
|
def find_todo(uid)
|
74
|
-
|
61
|
+
todos.find {|t| t.uid == uid}
|
75
62
|
end
|
76
63
|
|
77
64
|
def journal(&block)
|
78
|
-
|
79
|
-
self.add_component e
|
80
|
-
|
81
|
-
e.instance_eval(&block) if block
|
82
|
-
|
83
|
-
e
|
65
|
+
build_component Journal.new, &block
|
84
66
|
end
|
85
67
|
|
86
68
|
def find_journal(uid)
|
87
|
-
|
69
|
+
journals.find {|j| j.uid == uid}
|
88
70
|
end
|
89
71
|
|
90
72
|
def freebusy(&block)
|
91
|
-
|
92
|
-
self.add_component e
|
93
|
-
|
94
|
-
e.instance_eval(&block) if block
|
95
|
-
|
96
|
-
e
|
73
|
+
build_component Freebusy.new, &block
|
97
74
|
end
|
98
75
|
|
99
76
|
def find_freebusy(uid)
|
100
|
-
|
77
|
+
freebusys.find {|f| f.uid == uid}
|
101
78
|
end
|
102
79
|
|
103
80
|
def timezone(&block)
|
104
|
-
|
105
|
-
self.add_component e
|
106
|
-
|
107
|
-
e.instance_eval(&block) if block
|
108
|
-
|
109
|
-
e
|
81
|
+
build_component Timezone.new, &block
|
110
82
|
end
|
111
83
|
|
112
84
|
# The "PUBLISH" method in a "VEVENT" calendar component is an
|
@@ -122,6 +94,17 @@ module Icalendar
|
|
122
94
|
self.ip_method = "PUBLISH"
|
123
95
|
end
|
124
96
|
|
97
|
+
private
|
98
|
+
|
99
|
+
def build_component(component, &block)
|
100
|
+
add_component component
|
101
|
+
component.instance_eval(&block) if block
|
102
|
+
component
|
103
|
+
end
|
104
|
+
|
105
|
+
def timezone_id
|
106
|
+
timezones[0].tzid if timezones.length > 0
|
107
|
+
end
|
125
108
|
end # class Calendar
|
126
109
|
|
127
110
|
end # module Icalendar
|
data/lib/icalendar/component.rb
CHANGED
@@ -49,7 +49,7 @@ module Icalendar
|
|
49
49
|
|
50
50
|
def initialize(name)
|
51
51
|
@name = name
|
52
|
-
@components = Hash.new
|
52
|
+
@components = Hash.new { |h, k| h[k] = [] }
|
53
53
|
@properties = {}
|
54
54
|
|
55
55
|
@@logger.info("New #{@name[1,@name.size].capitalize}...")
|
@@ -57,13 +57,7 @@ module Icalendar
|
|
57
57
|
|
58
58
|
# Add a sub-component to the current component object.
|
59
59
|
def add_component(component)
|
60
|
-
|
61
|
-
|
62
|
-
unless @components.has_key? key
|
63
|
-
@components[key] = []
|
64
|
-
end
|
65
|
-
|
66
|
-
@components[key] << component
|
60
|
+
@components[component.key_name] << component
|
67
61
|
end
|
68
62
|
|
69
63
|
# Add a component to the calendar.
|
@@ -79,11 +73,7 @@ module Icalendar
|
|
79
73
|
alias add_journal add_component
|
80
74
|
|
81
75
|
def remove_component(component)
|
82
|
-
|
83
|
-
|
84
|
-
if @components.has_key? key
|
85
|
-
@components[key].delete(component)
|
86
|
-
end
|
76
|
+
@components[component.key_name].delete(component)
|
87
77
|
end
|
88
78
|
|
89
79
|
# Remove a component from the calendar.
|
@@ -105,95 +95,82 @@ module Icalendar
|
|
105
95
|
|
106
96
|
# Output in the icalendar format
|
107
97
|
def to_ical
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
end
|
113
|
-
s
|
98
|
+
printer do
|
99
|
+
[print_headers,
|
100
|
+
print_properties,
|
101
|
+
print_subcomponents].join
|
114
102
|
end
|
115
103
|
end
|
116
104
|
|
117
105
|
# Print this icalendar component
|
118
106
|
def print_component
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
# Then the properties
|
123
|
-
print_properties +
|
124
|
-
|
125
|
-
# sub components
|
126
|
-
yield +
|
107
|
+
to_ical
|
108
|
+
end
|
127
109
|
|
128
|
-
|
129
|
-
|
110
|
+
def print_subcomponents
|
111
|
+
@components.values.map do |component_parts|
|
112
|
+
Array(component_parts).map &:to_ical
|
113
|
+
end.join
|
130
114
|
end
|
131
115
|
|
132
|
-
def
|
133
|
-
|
116
|
+
def printer
|
117
|
+
["BEGIN:#{@name.upcase}\r\n",
|
118
|
+
yield,
|
119
|
+
"END:#{@name.upcase}\r\n"].join
|
120
|
+
end
|
134
121
|
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
122
|
+
def print_properties(properties = properties_to_print)
|
123
|
+
excludes = %w(geo rrule categories exdate)
|
124
|
+
properties.sort.map do |key, val|
|
125
|
+
property = fix_conflict_with_built_in(key)
|
126
|
+
prelude = property.gsub(/_/, '-').upcase
|
127
|
+
params = print_parameters(val)
|
128
|
+
|
129
|
+
value = ":#{val.to_ical}"
|
130
|
+
multiline = multiline_property?(property)
|
131
|
+
if multiline || (!multiline && !excludes.include?(property))
|
132
|
+
value = escape_chars(value)
|
140
133
|
end
|
141
134
|
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
else
|
151
|
-
prelude = "#{key.gsub(/_/, '-').upcase}"
|
152
|
-
val.each do |v|
|
153
|
-
params = print_parameters(v)
|
154
|
-
value = ":#{v.to_ical}"
|
155
|
-
value = escape_chars(value)
|
156
|
-
add_sliced_text(s, prelude + params + value)
|
157
|
-
end
|
158
|
-
end
|
159
|
-
end
|
160
|
-
s
|
135
|
+
chunk_lines(prelude + params + value)
|
136
|
+
end.join
|
137
|
+
end
|
138
|
+
|
139
|
+
# Take out underscore for property names that conflicted
|
140
|
+
# with built-in words.
|
141
|
+
def fix_conflict_with_built_in(key)
|
142
|
+
key.sub(/\Aip_/, '')
|
161
143
|
end
|
162
144
|
|
163
145
|
def escape_chars(value)
|
164
|
-
|
165
|
-
return v
|
166
|
-
# return value
|
146
|
+
value.gsub("\\", "\\\\").gsub("\r\n", "\n").gsub("\r", "\n").gsub("\n", "\\n").gsub(",", "\\,").gsub(";", "\\;")
|
167
147
|
end
|
168
148
|
|
169
|
-
def
|
170
|
-
|
171
|
-
|
172
|
-
|
149
|
+
def chunk_lines(str, length = MAX_LINE_LENGTH, separator = "\r\n ")
|
150
|
+
chunks = str.scan(/.{1,#{length}}/)
|
151
|
+
lines = chunks.join(separator) << separator
|
152
|
+
lines.gsub(/ *$/, '')
|
173
153
|
end
|
174
154
|
|
175
155
|
# Print the parameters for a specific property.
|
176
156
|
def print_parameters(value)
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
end
|
195
|
-
end
|
196
|
-
s
|
157
|
+
return "" unless value.respond_to?(:ical_params)
|
158
|
+
|
159
|
+
Array(value.ical_params).map do |key, val|
|
160
|
+
val = Array(val)
|
161
|
+
next if val.empty?
|
162
|
+
|
163
|
+
escaped = val.map { |v| Parser.escape(v.to_ical) }.join(',')
|
164
|
+
";#{key}=" << escaped
|
165
|
+
end.join
|
166
|
+
end
|
167
|
+
|
168
|
+
def properties_to_print
|
169
|
+
@properties # subclasses can exclude properties
|
170
|
+
end
|
171
|
+
|
172
|
+
def print_headers
|
173
|
+
"" # subclasses can specify headers
|
197
174
|
end
|
198
175
|
|
199
176
|
# TODO: Look into the x-property, x-param stuff...
|
@@ -214,6 +191,10 @@ module Icalendar
|
|
214
191
|
# Make it protected so we can monitor usage...
|
215
192
|
protected
|
216
193
|
|
194
|
+
def key_name
|
195
|
+
(self.class.to_s.downcase + 's').gsub('icalendar::', '').to_sym
|
196
|
+
end
|
197
|
+
|
217
198
|
def self.ical_component(*syms)
|
218
199
|
hash_accessor :@components, *syms
|
219
200
|
end
|
@@ -45,19 +45,6 @@ module Icalendar
|
|
45
45
|
@components[key] = component
|
46
46
|
end
|
47
47
|
|
48
|
-
# Also need a custom to_ical because typically it iterates over an array
|
49
|
-
# of components.
|
50
|
-
def to_ical
|
51
|
-
print_component do
|
52
|
-
s = ""
|
53
|
-
@components.each_value do |comp|
|
54
|
-
s << comp.to_ical
|
55
|
-
end
|
56
|
-
s
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
|
-
|
61
48
|
def initialize(name = "VTIMEZONE")
|
62
49
|
super(name)
|
63
50
|
end
|
data/lib/icalendar/parser.rb
CHANGED
@@ -65,6 +65,19 @@ module Icalendar
|
|
65
65
|
@@logger.debug("New Calendar Parser: #{@file.inspect}")
|
66
66
|
end
|
67
67
|
|
68
|
+
def self.escape(value)
|
69
|
+
if value =~ %r{\A#{Parser::QSTR}\z|\A#{Parser::PTEXT}\z}
|
70
|
+
value
|
71
|
+
else
|
72
|
+
stripped = value.gsub '"', "'"
|
73
|
+
if stripped =~ /\A#{Parser::PTEXT}\z/
|
74
|
+
stripped
|
75
|
+
else
|
76
|
+
%|"#{stripped}"|
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
68
81
|
# Define next line for an IO object.
|
69
82
|
# Works for strings now with StringIO
|
70
83
|
def next_line
|
data/test/test_calendar.rb
CHANGED
@@ -45,6 +45,7 @@ class TestCalendar < Test::Unit::TestCase
|
|
45
45
|
|
46
46
|
def test_create_multiple_event_calendar
|
47
47
|
# Create a fresh calendar
|
48
|
+
Timecop.freeze DateTime.new(2013, 12, 26, 5, 0, 0, '+0000')
|
48
49
|
cal = Calendar.new
|
49
50
|
[1,2,3].each do |t|
|
50
51
|
cal.event do
|
@@ -57,7 +58,49 @@ class TestCalendar < Test::Unit::TestCase
|
|
57
58
|
self.summary = "test #{t} todo"
|
58
59
|
end
|
59
60
|
end
|
60
|
-
|
61
|
+
expected_no_uid = <<-EXPECTED.gsub("\n", "\r\n")
|
62
|
+
BEGIN:VCALENDAR
|
63
|
+
VERSION:2.0
|
64
|
+
CALSCALE:GREGORIAN
|
65
|
+
PRODID:iCalendar-Ruby
|
66
|
+
BEGIN:VEVENT
|
67
|
+
DTEND:19970901T190000Z
|
68
|
+
DTSTAMP:20131226T050000Z
|
69
|
+
SEQUENCE:0
|
70
|
+
SUMMARY:This is summary 1
|
71
|
+
END:VEVENT
|
72
|
+
BEGIN:VEVENT
|
73
|
+
DTEND:19970902T190000Z
|
74
|
+
DTSTAMP:20131226T050000Z
|
75
|
+
SEQUENCE:0
|
76
|
+
SUMMARY:This is summary 2
|
77
|
+
END:VEVENT
|
78
|
+
BEGIN:VEVENT
|
79
|
+
DTEND:19970903T190000Z
|
80
|
+
DTSTAMP:20131226T050000Z
|
81
|
+
SEQUENCE:0
|
82
|
+
SUMMARY:This is summary 3
|
83
|
+
END:VEVENT
|
84
|
+
BEGIN:VTODO
|
85
|
+
DTSTAMP:20131226T050000Z
|
86
|
+
SEQUENCE:0
|
87
|
+
SUMMARY:test 1 todo
|
88
|
+
END:VTODO
|
89
|
+
BEGIN:VTODO
|
90
|
+
DTSTAMP:20131226T050000Z
|
91
|
+
SEQUENCE:0
|
92
|
+
SUMMARY:test 2 todo
|
93
|
+
END:VTODO
|
94
|
+
BEGIN:VTODO
|
95
|
+
DTSTAMP:20131226T050000Z
|
96
|
+
SEQUENCE:0
|
97
|
+
SUMMARY:test 3 todo
|
98
|
+
END:VTODO
|
99
|
+
END:VCALENDAR
|
100
|
+
EXPECTED
|
101
|
+
actual_no_uid = cal.to_ical.gsub /^UID:.*\r\n(?: .*\r\n)*/, ''
|
102
|
+
Timecop.return
|
103
|
+
assert_equal expected_no_uid, actual_no_uid
|
61
104
|
end
|
62
105
|
|
63
106
|
def test_find
|
data/test/test_parameter.rb
CHANGED
@@ -40,9 +40,11 @@ class TestParameter < Test::Unit::TestCase
|
|
40
40
|
|
41
41
|
def test_unquoted_property_parameters
|
42
42
|
params = {'ALTREP' => ['"http://my.language.net"'],
|
43
|
-
'LANGUAGE' => ['SPANISH:CATILLAN']
|
43
|
+
'LANGUAGE' => ['SPANISH:CATILLAN'],
|
44
|
+
'CN' => ['Joe "The Man" Tester']}
|
44
45
|
expected_params = {'ALTREP' => ['"http://my.language.net"'],
|
45
|
-
'LANGUAGE' => ['"SPANISH:CATILLAN"']
|
46
|
+
'LANGUAGE' => ['"SPANISH:CATILLAN"'],
|
47
|
+
'CN' => ["Joe 'The Man' Tester"]}
|
46
48
|
@event.summary('This is a test summary.', params)
|
47
49
|
|
48
50
|
assert_equal params, @event.summary.ical_params
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: icalendar
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.5.
|
4
|
+
version: 1.5.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ryan Ahearn
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2014-02-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|