icalendar 2.3.0 → 2.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3ed623b569af5c7f68aa5917f19acedb5a11f7e8
4
- data.tar.gz: 3293337f990892e2c07cc9c8d23f5bf4b86e5a37
3
+ metadata.gz: 851d909893eaa853f0196567cf367c7062e725f8
4
+ data.tar.gz: d218b26b7f7b9b8f004308fab26290538c542e0b
5
5
  SHA512:
6
- metadata.gz: 4a95ab23132eeffa95b42b1c2da033aa46b92775caf43928458ffd048685f66095855235b1b951f1bb18a1a33b7fb817cc1e4fc3f1b4917a277a3f18306dc592
7
- data.tar.gz: 8d77c499752d3f589b9dca961d61df24f9b67a89f4764c462e71bcedd313a0f8d3b1aace26849bbe183a415235e4c6b159b8ea4b194b37cf9b202a73f001c568
6
+ metadata.gz: 7e47ce5836cf569bc8a8dfa79d642093302587ae6cff544162ad9f26197c20df73d3062e1bb74a52e21c9a765a0fa8ce7469f9c3342520528bbd660647249616
7
+ data.tar.gz: fd89f85a3cb86ebb50dbfff5db479bfcf7232c7f7ef061ae2d60fcb5d8cb48bb876aff4604b3ec50e2bcecfa25b2d86a0374de5c9d473d09ba616a52377f2421
@@ -1,9 +1,12 @@
1
+ sudo: false
2
+ before_install:
3
+ - gem install bundler
1
4
  language: ruby
2
5
  rvm:
6
+ - 2.3.1
7
+ - 2.2
3
8
  - 2.1
4
9
  - 2.0
5
- - 1.9.3
6
- - 1.9.2
7
10
  - jruby-19mode
8
11
  - rbx-2
9
12
  - ruby-head
@@ -1,3 +1,8 @@
1
+ === 2.4.0 2015-07-04
2
+ * Enable parsing individual ICalendar components - Patrick Schnetger
3
+ * many bug fixes. Thanks to Quan Sun, Garry Shutler, Ryan Bigg, Patrick Schnetger and others
4
+ * README/documentation updates. Thanks to JonMidhir and Hendrik Sollich
5
+
1
6
  === 2.3.0 2015-04-26
2
7
  * fix value parameter for properties with multiple values
3
8
  * fix error when assigning Icalendar::Values::Array to a component
data/README.md CHANGED
@@ -38,51 +38,79 @@ EXAMPLES
38
38
 
39
39
  ### Creating calendars and events ###
40
40
 
41
- require 'icalendar'
42
-
43
- # Create a calendar with an event (standard method)
44
- cal = Icalendar::Calendar.new
45
- cal.event do |e|
46
- e.dtstart = Icalendar::Values::Date.new('20050428')
47
- e.dtend = Icalendar::Values::Date.new('20050429')
48
- e.summary = "Meeting with the man."
49
- e.description = "Have a long lunch meeting and decide nothing..."
50
- e.ip_class = "PRIVATE"
51
- end
52
-
53
- cal.publish
41
+ ```ruby
42
+ require 'icalendar'
43
+
44
+ # Create a calendar with an event (standard method)
45
+ cal = Icalendar::Calendar.new
46
+ cal.event do |e|
47
+ e.dtstart = Icalendar::Values::Date.new('20050428')
48
+ e.dtend = Icalendar::Values::Date.new('20050429')
49
+ e.summary = "Meeting with the man."
50
+ e.description = "Have a long lunch meeting and decide nothing..."
51
+ e.ip_class = "PRIVATE"
52
+ end
53
+
54
+ cal.publish
55
+ ```
54
56
 
55
57
  #### Or you can make events like this ####
56
58
 
57
- event = Icalendar::Event.new
58
- event.dtstart = DateTime.civil(2006, 6, 23, 8, 30)
59
- event.summary = "A great event!"
60
- cal.add_event(event)
59
+ ```ruby
60
+ event = Icalendar::Event.new
61
+ event.dtstart = DateTime.civil(2006, 6, 23, 8, 30)
62
+ event.summary = "A great event!"
63
+ cal.add_event(event)
61
64
 
62
- event2 = cal.event # This automatically adds the event to the calendar
63
- event2.dtstart = DateTime.civil(2006, 6, 24, 8, 30)
64
- event2.summary = "Another great event!"
65
+ event2 = cal.event # This automatically adds the event to the calendar
66
+ event2.dtstart = DateTime.civil(2006, 6, 24, 8, 30)
67
+ event2.summary = "Another great event!"
68
+ ```
65
69
 
66
70
  #### Support for property parameters ####
67
71
 
68
- params = {"altrep" => "http://my.language.net", "language" => "SPANISH"}
72
+ ```ruby
73
+ params = {"altrep" => "http://my.language.net", "language" => "SPANISH"}
74
+
75
+ event = cal.event do |e|
76
+ e.dtstart = Icalendar::Values::Date.new('20050428')
77
+ e.dtend = Icalendar::Values::Date.new('20050429')
78
+ e.summary = Icalendar::Values::Text.new "This is a summary with params.", params
79
+ end
80
+ event.summary.ical_params #=> {'altrep' => 'http://my.language.net', 'language' => 'SPANISH'}
81
+
82
+ # or
83
+
84
+ event = cal.event do |e|
85
+ e.dtstart = Icalendar::Values::Date.new('20050428')
86
+ e.dtend = Icalendar::Values::Date.new('20050429')
87
+ e.summary = "This is a summary with params."
88
+ e.summary.ical_params = params
89
+ end
90
+ event.summary.ical_params #=> {'altrep' => 'http://my.language.net', 'language' => 'SPANISH'}
91
+ ```
92
+
93
+ #### Support for Dates or DateTimes
69
94
 
70
- event = cal.event do |e|
71
- e.dtstart = Icalendar::Values::Date.new('20050428')
72
- e.dtend = Icalendar::Values::Date.new('20050429')
73
- e.summary = Icalendar::Values::Text.new "This is a summary with params.", params
74
- end
75
- event.summary.ical_params #=> {'altrep' => 'http://my.language.net', 'language' => 'SPANISH'}
95
+ Sometimes we don't care if an event's start or end are `Date` or `DateTime` objects. For this, we can use `DateOrDateTime.new(value).call`
76
96
 
77
- # or
97
+ ```ruby
98
+ event = cal.event do |e|
99
+ e.dtstart = Icalendar::Values::DateOrDateTime.new('20140924').call
100
+ e.dtend = Icalendar::Values::DateOrDateTime.new('20140924').call
101
+ e.summary = 'This is an all-day event, because DateOrDateTime will return Dates'
102
+ end
103
+ ```
78
104
 
79
- event = cal.event do |e|
80
- e.dtstart = Icalendar::Values::Date.new('20050428')
81
- e.dtend = Icalendar::Values::Date.new('20050429')
82
- e.summary = "This is a summary with params."
83
- e.summary.ical_params = params
84
- end
85
- event.summary.ical_params #=> {'altrep' => 'http://my.language.net', 'language' => 'SPANISH'}
105
+ #### Support for URLs
106
+
107
+ For clients that can parse and display a URL associated with an event, it's possible to assign one.
108
+
109
+ ```ruby
110
+ event = cal.event do |e|
111
+ e.url = 'https://example.com'
112
+ end
113
+ ```
86
114
 
87
115
  #### We can output the calendar as a string ####
88
116
 
@@ -94,30 +122,32 @@ ALARMS
94
122
 
95
123
  ### Within an event ###
96
124
 
97
- cal.event do |e|
98
- # ...other event properties
99
- e.alarm do |a|
100
- a.action = "EMAIL"
101
- a.description = "This is an event reminder" # email body (required)
102
- a.summary = "Alarm notification" # email subject (required)
103
- a.attendee = %w(mailto:me@my-domain.com mailto:me-too@my-domain.com) # one or more email recipients (required)
104
- a.append_attendee "mailto:me-three@my-domain.com"
105
- a.trigger = "-PT15M" # 15 minutes before
106
- a.append_attach Icalendar::Values::Uri.new "ftp://host.com/novo-procs/felizano.exe", "fmttype" => "application/binary" # email attachments (optional)
107
- end
108
-
109
- e.alarm do |a|
110
- a.action = "DISPLAY" # This line isn't necessary, it's the default
111
- a.summary = "Alarm notification"
112
- a.trigger = "-P1DT0H0M0S" # 1 day before
113
- end
114
-
115
- e.alarm do |a|
116
- a.action = "AUDIO"
117
- a.trigger = "-PT15M"
118
- a.append_attach "Basso"
119
- end
120
- end
125
+ ```ruby
126
+ cal.event do |e|
127
+ # ...other event properties
128
+ e.alarm do |a|
129
+ a.action = "EMAIL"
130
+ a.description = "This is an event reminder" # email body (required)
131
+ a.summary = "Alarm notification" # email subject (required)
132
+ a.attendee = %w(mailto:me@my-domain.com mailto:me-too@my-domain.com) # one or more email recipients (required)
133
+ a.append_attendee "mailto:me-three@my-domain.com"
134
+ a.trigger = "-PT15M" # 15 minutes before
135
+ a.append_attach Icalendar::Values::Uri.new "ftp://host.com/novo-procs/felizano.exe", "fmttype" => "application/binary" # email attachments (optional)
136
+ end
137
+
138
+ e.alarm do |a|
139
+ a.action = "DISPLAY" # This line isn't necessary, it's the default
140
+ a.summary = "Alarm notification"
141
+ a.trigger = "-P1DT0H0M0S" # 1 day before
142
+ end
143
+
144
+ e.alarm do |a|
145
+ a.action = "AUDIO"
146
+ a.trigger = "-PT15M"
147
+ a.append_attach "Basso"
148
+ end
149
+ end
150
+ ```
121
151
 
122
152
  #### Output ####
123
153
 
@@ -147,38 +177,42 @@ ALARMS
147
177
 
148
178
  Calling the `event.alarm` method will create an alarm if one doesn't exist. To check if an event has an alarm use the `has_alarm?` method.
149
179
 
150
- event.has_alarm?
151
- # => false
152
-
153
- event.alarm
154
- # => #<Icalendar::Alarm ... >
155
-
156
- event.has_alarm?
157
- #=> true
180
+ ```ruby
181
+ event.has_alarm?
182
+ # => false
183
+
184
+ event.alarm
185
+ # => #<Icalendar::Alarm ... >
186
+
187
+ event.has_alarm?
188
+ #=> true
189
+ ```
158
190
 
159
191
  TIMEZONES
160
192
  ---
161
193
 
162
- cal = Icalendar::Calendar.new
163
- cal.timezone do |t|
164
- t.tzid = "America/Chicago"
165
-
166
- t.daylight do |d|
167
- d.tzoffsetfrom = "-0600"
168
- d.tzoffsetto = "-0500"
169
- d.tzname = "CDT"
170
- d.dtstart = "19700308T020000"
171
- d.rrule = "FREQ=YEARLY;BYMONTH=3;BYDAY=2SU"
172
- end
173
-
174
- t.standard do |s|
175
- s.tzoffsetfrom = "-0500"
176
- s.tzoffsetto = "-0600"
177
- s.tzname = "CST"
178
- s.dtstart = "19701101T020000"
179
- s.rrule = "FREQ=YEARLY;BYMONTH=11;BYDAY=1SU"
180
- end
181
- end
194
+ ```ruby
195
+ cal = Icalendar::Calendar.new
196
+ cal.timezone do |t|
197
+ t.tzid = "America/Chicago"
198
+
199
+ t.daylight do |d|
200
+ d.tzoffsetfrom = "-0600"
201
+ d.tzoffsetto = "-0500"
202
+ d.tzname = "CDT"
203
+ d.dtstart = "19700308T020000"
204
+ d.rrule = "FREQ=YEARLY;BYMONTH=3;BYDAY=2SU"
205
+ end
206
+
207
+ t.standard do |s|
208
+ s.tzoffsetfrom = "-0500"
209
+ s.tzoffsetto = "-0600"
210
+ s.tzname = "CST"
211
+ s.dtstart = "19701101T020000"
212
+ s.rrule = "FREQ=YEARLY;BYMONTH=11;BYDAY=1SU"
213
+ end
214
+ end
215
+ ```
182
216
 
183
217
  #### Output ####
184
218
 
@@ -207,53 +241,76 @@ iCalendar has been tested and works with `tzinfo` versions 0.3 and 1.1
207
241
 
208
242
  #### Example ####
209
243
 
210
- require 'icalendar/tzinfo'
244
+ ```ruby
245
+ require 'icalendar/tzinfo'
211
246
 
212
- cal = Icalendar::Calendar.new
247
+ cal = Icalendar::Calendar.new
213
248
 
214
- event_start = DateTime.new 2008, 12, 29, 8, 0, 0
215
- event_end = DateTime.new 2008, 12, 29, 11, 0, 0
249
+ event_start = DateTime.new 2008, 12, 29, 8, 0, 0
250
+ event_end = DateTime.new 2008, 12, 29, 11, 0, 0
216
251
 
217
- tzid = "America/Chicago"
218
- tz = TZInfo::Timezone.get tzid
219
- timezone = tz.ical_timezone event_start
220
- cal.add_timezone timezone
252
+ tzid = "America/Chicago"
253
+ tz = TZInfo::Timezone.get tzid
254
+ timezone = tz.ical_timezone event_start
255
+ cal.add_timezone timezone
221
256
 
222
- cal.event do |e|
223
- e.dtstart = Icalendar::Values::DateTime.new event_start, 'tzid' => tzid
224
- e.dtend = Icalendar::Values::DateTime.new event_end, 'tzid' => tzid
225
- e.summary = "Meeting with the man."
226
- e.description = "Have a long lunch meeting and decide nothing..."
227
- e.organizer = "mailto:jsmith@example.com"
228
- e.organizer = Icalendar::Values::CalAddress.new("mailto:jsmith@example.com", cn: 'John Smith')
229
- end
257
+ cal.event do |e|
258
+ e.dtstart = Icalendar::Values::DateTime.new event_start, 'tzid' => tzid
259
+ e.dtend = Icalendar::Values::DateTime.new event_end, 'tzid' => tzid
260
+ e.summary = "Meeting with the man."
261
+ e.description = "Have a long lunch meeting and decide nothing..."
262
+ e.organizer = "mailto:jsmith@example.com"
263
+ e.organizer = Icalendar::Values::CalAddress.new("mailto:jsmith@example.com", cn: 'John Smith')
264
+ end
265
+ ```
230
266
 
231
267
 
232
268
  Parsing iCalendars
233
269
  ---
234
270
 
235
- # Open a file or pass a string to the parser
236
- cal_file = File.open("single_event.ics")
271
+ ```ruby
272
+ # Open a file or pass a string to the parser
273
+ cal_file = File.open("single_event.ics")
237
274
 
238
- # Parser returns an array of calendars because a single file
239
- # can have multiple calendars.
240
- cals = Icalendar.parse(cal_file)
241
- cal = cals.first
275
+ # Parser returns an array of calendars because a single file
276
+ # can have multiple calendars.
277
+ cals = Icalendar::Calendar.parse(cal_file)
278
+ cal = cals.first
242
279
 
243
- # Now you can access the cal object in just the same way I created it
244
- event = cal.events.first
280
+ # Now you can access the cal object in just the same way I created it
281
+ event = cal.events.first
245
282
 
246
- puts "start date-time: #{event.dtstart}"
247
- puts "start date-time timezone: #{event.dtstart.ical_params['tzid']}"
248
- puts "summary: #{event.summary}"
283
+ puts "start date-time: #{event.dtstart}"
284
+ puts "start date-time timezone: #{event.dtstart.ical_params['tzid']}"
285
+ puts "summary: #{event.summary}"
286
+ ```
249
287
 
250
288
  You can also create a `Parser` instance directly, this can be used to enable
251
289
  strict parsing:
252
290
 
253
- # Sometimes you want to strongly verify only rfc-approved properties are
254
- # used
255
- strict_parser = Icalendar::Parser.new(cal_file, true)
256
- cal = strict_parser.parse
291
+ ```ruby
292
+ # Sometimes you want to strongly verify only rfc-approved properties are
293
+ # used
294
+ strict_parser = Icalendar::Parser.new(cal_file, true)
295
+ cal = strict_parser.parse
296
+ ```
297
+
298
+ Parsing Components (e.g. Events)
299
+ ---
300
+
301
+ ```ruby
302
+ # Open a file or pass a string to the parser
303
+ event_file = File.open("event.ics")
304
+
305
+ # Parser returns an array of events because a single file
306
+ # can have multiple events.
307
+ events = Icalendar::Event.parse(event_file)
308
+ event = events.first
309
+
310
+ puts "start date-time: #{event.dtstart}"
311
+ puts "start date-time timezone: #{event.dtstart.ical_params['tzid']}"
312
+ puts "summary: #{event.summary}"
313
+ ```
257
314
 
258
315
  Finders
259
316
  ---
@@ -262,15 +319,17 @@ Often times in web apps and other interactive applications you'll need to
262
319
  lookup items in a calendar to make changes or get details. Now you can find
263
320
  everything by the unique id automatically associated with all components.
264
321
 
265
- cal = Calendar.new
266
- 10.times { cal.event } # Create 10 events with only default data.
267
- some_event = cal.events[5] # Grab it from the array of events
322
+ ```ruby
323
+ cal = Calendar.new
324
+ 10.times { cal.event } # Create 10 events with only default data.
325
+ some_event = cal.events[5] # Grab it from the array of events
268
326
 
269
- # Use the uid as the key in your app
270
- key = some_event.uid
327
+ # Use the uid as the key in your app
328
+ key = some_event.uid
271
329
 
272
- # so later you can find it.
273
- same_event = cal.find_event(key)
330
+ # so later you can find it.
331
+ same_event = cal.find_event(key)
332
+ ```
274
333
 
275
334
  Examples
276
335
  ---
@@ -311,3 +370,20 @@ Support & Contributions
311
370
 
312
371
  Please submit pull requests from a rebased topic branch and
313
372
  include tests for all bugs and features.
373
+
374
+ Contributor Code of Conduct
375
+ ---
376
+
377
+ As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
378
+
379
+ We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, or religion.
380
+
381
+ Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.
382
+
383
+ Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team.
384
+
385
+ This code of conduct applies both within project spaces and in public spaces when an individual is representing the project or its community.
386
+
387
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.
388
+
389
+ This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.1.0, available at [http://contributor-covenant.org/version/1/1/0/](http://contributor-covenant.org/version/1/1/0/)
@@ -47,6 +47,6 @@ ActiveSupport is required for TimeWithZone support, but not required for general
47
47
  s.add_development_dependency 'i18n', '< 0.7.0'
48
48
 
49
49
  s.add_development_dependency 'timecop', '~> 0.7.0'
50
- s.add_development_dependency 'rspec', '~> 2.14'
50
+ s.add_development_dependency 'rspec', '~> 3.0'
51
51
  s.add_development_dependency 'simplecov', '~> 0.8'
52
52
  end
@@ -13,6 +13,7 @@ module Icalendar
13
13
  end
14
14
 
15
15
  def self.parse(source, single = false)
16
+ warn "**** DEPRECATION WARNING ****\nIcalender.parse will be removed. Please switch to Icalendar::Calendar.parse."
16
17
  calendars = Parser.new(source).parse
17
18
  single ? calendars.first : calendars
18
19
  end
@@ -22,6 +22,7 @@ module Icalendar
22
22
  def publish
23
23
  self.ip_method = 'PUBLISH'
24
24
  end
25
+
25
26
  end
26
27
 
27
28
  end
@@ -10,6 +10,12 @@ module Icalendar
10
10
  attr_reader :ical_name
11
11
  attr_accessor :parent
12
12
 
13
+ def self.parse(source)
14
+ parser = Parser.new(source)
15
+ parser.component = self.new
16
+ parser.parse
17
+ end
18
+
13
19
  def initialize(name, ical_name = nil)
14
20
  @name = name
15
21
  @ical_name = ical_name || "V#{name.upcase}"
@@ -141,7 +141,12 @@ module Icalendar
141
141
  property_var = "@#{prop}"
142
142
 
143
143
  define_method "#{prop}=" do |value|
144
- instance_variable_set property_var, Array(map_property_value(value, klass, true)).compact
144
+ mapped = map_property_value value, klass, true
145
+ if mapped.is_a? Icalendar::Values::Array
146
+ instance_variable_set property_var, mapped.to_a.compact
147
+ else
148
+ instance_variable_set property_var, [mapped].compact
149
+ end
145
150
  end
146
151
 
147
152
  define_method prop do
@@ -1,7 +1,7 @@
1
1
  module Icalendar
2
2
 
3
3
  class Parser
4
-
4
+ attr_writer :component
5
5
  attr_reader :source, :strict
6
6
 
7
7
  def initialize(source, strict = false)
@@ -21,21 +21,22 @@ module Icalendar
21
21
  def parse
22
22
  source.rewind
23
23
  read_in_data
24
- calendars = []
24
+ components = []
25
25
  while (fields = next_fields)
26
- if fields[:name] == 'begin' && fields[:value].downcase == 'vcalendar'
27
- calendars << parse_component(Calendar.new)
26
+ if fields[:name] == 'begin' && fields[:value].downcase == component.ical_name.downcase
27
+ components << parse_component(component)
28
28
  end
29
29
  end
30
- calendars
30
+ components
31
31
  end
32
32
 
33
33
  def parse_property(component, fields = nil)
34
34
  fields = next_fields if fields.nil?
35
- prop_value = wrap_property_value component, fields
36
35
  prop_name = %w(class method).include?(fields[:name]) ? "ip_#{fields[:name]}" : fields[:name]
36
+ multi_property = component.class.multiple_properties.include? prop_name
37
+ prop_value = wrap_property_value component, fields, multi_property
37
38
  begin
38
- method_name = if component.class.multiple_properties.include? prop_name
39
+ method_name = if multi_property
39
40
  "append_#{prop_name}"
40
41
  else
41
42
  "#{prop_name}="
@@ -52,10 +53,10 @@ module Icalendar
52
53
  end
53
54
  end
54
55
 
55
- def wrap_property_value(component, fields)
56
+ def wrap_property_value(component, fields, multi_property)
56
57
  klass = get_wrapper_class component, fields
57
- if klass.value_type != 'RECUR' && fields[:value] =~ /(?<!\\)([,;])/
58
- delimiter = $1
58
+ if wrap_in_array? klass, fields[:value], multi_property
59
+ delimiter = fields[:value].match(/(?<!\\)([,;])/)[1]
59
60
  Icalendar::Values::Array.new fields[:value].split(/(?<!\\)[;,]/),
60
61
  klass,
61
62
  fields[:params],
@@ -69,6 +70,11 @@ module Icalendar
69
70
  retry
70
71
  end
71
72
 
73
+ def wrap_in_array?(klass, value, multi_property)
74
+ klass.value_type != 'RECUR' &&
75
+ ((multi_property && value =~ /(?<!\\)[,;]/) || value =~ /(?<!\\);/)
76
+ end
77
+
72
78
  def get_wrapper_class(component, fields)
73
79
  klass = component.class.default_property_types[fields[:name]]
74
80
  if !fields[:params]['value'].nil?
@@ -87,6 +93,10 @@ module Icalendar
87
93
 
88
94
  private
89
95
 
96
+ def component
97
+ @component ||= Icalendar::Calendar.new
98
+ end
99
+
90
100
  def parse_component(component)
91
101
  while (fields = next_fields)
92
102
  if fields[:name] == 'end'
@@ -55,7 +55,7 @@ module Icalendar
55
55
  end
56
56
 
57
57
  def escape_param_value(value)
58
- v = value.gsub '"', "'"
58
+ v = value.to_s.gsub('"', "'")
59
59
  v =~ /[;:,]/ ? %("#{v}") : v
60
60
  end
61
61
 
@@ -63,8 +63,9 @@ module Icalendar
63
63
 
64
64
  end
65
65
 
66
- # helper; not actual iCalendar value type
66
+ # helpers; not actual iCalendar value type
67
67
  require_relative 'values/array'
68
+ require_relative 'values/date_or_date_time'
68
69
 
69
70
  # iCalendar value types
70
71
  require_relative 'values/binary'
@@ -11,6 +11,10 @@ module Icalendar
11
11
  value.map do |v|
12
12
  if v.is_a? Icalendar::Values::Array
13
13
  Icalendar::Values::Array.new v.value, klass, v.ical_params, delimiter: v.value_delimiter
14
+ elsif v.is_a? ::Array
15
+ Icalendar::Values::Array.new v, klass, params, delimiter: value_delimiter
16
+ elsif v.is_a? Icalendar::Value
17
+ v
14
18
  else
15
19
  klass.new v, params
16
20
  end
@@ -8,7 +8,13 @@ module Icalendar
8
8
 
9
9
  def initialize(value, params = {})
10
10
  if value.is_a? String
11
- super ::Date.strptime(value, FORMAT), params
11
+ begin
12
+ parsed_date = ::Date.strptime(value, FORMAT)
13
+ rescue ArgumentError => e
14
+ raise FormatError.new("Failed to parse \"#{value}\" - #{e.message}")
15
+ end
16
+
17
+ super parsed_date, params
12
18
  elsif value.respond_to? :to_date
13
19
  super value.to_date, params
14
20
  else
@@ -28,6 +34,8 @@ module Icalendar
28
34
  end
29
35
  end
30
36
 
37
+ class FormatError < ArgumentError
38
+ end
31
39
  end
32
40
 
33
41
  end
@@ -0,0 +1,29 @@
1
+ module Icalendar
2
+ module Values
3
+
4
+ # DateOrDateTime can be used to set an attribute to either a Date or a DateTime value.
5
+ # It should not be used wihtout also invoking the `call` method.
6
+ class DateOrDateTime
7
+
8
+ attr_reader :value, :params, :parsed
9
+ def initialize(value, params = {})
10
+ @value = value
11
+ @params = params
12
+ end
13
+
14
+ def call
15
+ @parsed ||= begin
16
+ Icalendar::Values::DateTime.new value, params
17
+ rescue Icalendar::Values::DateTime::FormatError
18
+ Icalendar::Values::Date.new value, params
19
+ end
20
+ end
21
+
22
+ def to_ical
23
+ fail NoMethodError, 'You cannot use DateOrDateTime directly. Invoke `call` before `to_ical`'
24
+ end
25
+
26
+ end
27
+
28
+ end
29
+ end
@@ -1,5 +1,5 @@
1
1
  module Icalendar
2
2
 
3
- VERSION = '2.3.0'
3
+ VERSION = '2.4.0'
4
4
 
5
5
  end
@@ -71,7 +71,7 @@ describe Icalendar::Calendar do
71
71
 
72
72
  describe '#add_event' do
73
73
  it 'delegates to non add_ version' do
74
- subject.should_receive(:event).with ical_component
74
+ expect(subject).to receive(:event).with(ical_component)
75
75
  subject.add_event ical_component
76
76
  end
77
77
  end
@@ -96,6 +96,18 @@ describe Icalendar::Event do
96
96
  end
97
97
  end
98
98
 
99
+ describe '.parse' do
100
+ let(:source) { File.read File.join(File.dirname(__FILE__), 'fixtures', fn) }
101
+ let(:fn) { 'event.ics' }
102
+
103
+ it 'should return an events array' do
104
+ events = Icalendar::Event.parse(source)
105
+ expect(events).to be_instance_of Array
106
+ expect(events.count).to be 1
107
+ expect(events.first).to be_instance_of Icalendar::Event
108
+ end
109
+ end
110
+
99
111
  describe '#find_alarm' do
100
112
  it 'should not respond_to find_alarm' do
101
113
  expect(subject.respond_to?(:find_alarm)).to be false
@@ -0,0 +1,17 @@
1
+ BEGIN:VEVENT
2
+ DTSTAMP:20050118T211523Z
3
+ UID:bsuidfortestabc123
4
+ DTSTART;TZID=US-Mountain:20050120T170000
5
+ DTEND;TZID=US-Mountain:20050120T184500
6
+ CLASS:PRIVATE
7
+ GEO:37.386013;-122.0829322
8
+ ORGANIZER:mailto:joebob@random.net
9
+ PRIORITY:2
10
+ SUMMARY:This is a really long summary to test the method of unfolding lines
11
+ \, so I'm just going to make it a whole bunch of lines.
12
+ ATTACH:http://bush.sucks.org/impeach/him.rhtml
13
+ ATTACH:http://corporations-dominate.existence.net/why.rhtml
14
+ RDATE;TZID=US-Mountain:20050121T170000,20050122T170000
15
+ X-TEST-COMPONENT;QTEST="Hello, World":Shouldn't double double quotes
16
+ END:VEVENT
17
+
@@ -32,6 +32,17 @@ describe Icalendar::Parser do
32
32
  expect(ics).to match 'EXDATE;VALUE=DATE:20120323,20130323'
33
33
  end
34
34
  end
35
+ context 'event.ics' do
36
+ let(:fn) { 'event.ics' }
37
+
38
+ before { subject.component = Icalendar::Event.new }
39
+
40
+ it 'returns an array of events' do
41
+ expect(subject.parse).to be_instance_of Array
42
+ expect(subject.parse.count).to be 1
43
+ expect(subject.parse[0]).to be_instance_of Icalendar::Event
44
+ end
45
+ end
35
46
  end
36
47
 
37
48
  describe '#parse with bad line' do
@@ -6,12 +6,13 @@ describe Icalendar do
6
6
  let(:source) { File.read File.join(File.dirname(__FILE__), 'fixtures', 'single_event.ics') }
7
7
 
8
8
  it 'will generate the same file as is parsed' do
9
- expect(Icalendar.parse(source, true).to_ical).to eq source
9
+ ical = Icalendar::Calendar.parse(source).first.to_ical
10
+ expect(ical).to eq source
10
11
  end
11
12
 
12
13
  it 'array properties can be assigned to a new event' do
13
14
  event = Icalendar::Event.new
14
- parsed = Icalendar.parse source, true
15
+ parsed = Icalendar::Calendar.parse(source).first
15
16
  event.rdate = parsed.events.first.rdate
16
17
  expect(event.rdate.first).to be_kind_of Icalendar::Values::Array
17
18
  expect(event.rdate.first.ical_params).to eq 'tzid' => ['US-Mountain']
@@ -21,13 +22,14 @@ describe Icalendar do
21
22
  describe 'timezone round trip' do
22
23
  let(:source) { File.read File.join(File.dirname(__FILE__), 'fixtures', 'timezone.ics') }
23
24
  it 'will generate the same file as it parsed' do
24
- expect(Icalendar.parse(source, true).to_ical).to eq source
25
+ ical = Icalendar::Calendar.parse(source).first.to_ical
26
+ expect(ical).to eq source
25
27
  end
26
28
  end
27
29
 
28
30
  describe 'non-default values' do
29
31
  let(:source) { File.read File.join(File.dirname(__FILE__), 'fixtures', 'nondefault_values.ics') }
30
- subject { Icalendar.parse(source, true).events.first }
32
+ subject { Icalendar::Calendar.parse(source).first.events.first }
31
33
 
32
34
  it 'will set dtstart to Date' do
33
35
  expect(subject.dtstart.value).to eq ::Date.new(2006, 12, 15)
@@ -48,7 +50,7 @@ describe Icalendar do
48
50
 
49
51
  describe 'sorting daily events' do
50
52
  let(:source) { File.read File.join(File.dirname(__FILE__), 'fixtures', 'two_day_events.ics') }
51
- subject { Icalendar.parse(source, true).events }
53
+ subject { Icalendar::Calendar.parse(source).first.events }
52
54
 
53
55
  it 'sorts day events' do
54
56
  events = subject.sort_by(&:dtstart)
@@ -60,7 +62,7 @@ describe Icalendar do
60
62
 
61
63
  describe 'sorting time events' do
62
64
  let(:source) { File.read File.join(File.dirname(__FILE__), 'fixtures', 'two_time_events.ics') }
63
- subject { Icalendar.parse(source, true).events }
65
+ subject { Icalendar::Calendar.parse(source).first.events }
64
66
 
65
67
  it 'sorts time events by start time' do
66
68
  events = subject.sort_by(&:dtstart)
@@ -82,7 +84,7 @@ describe Icalendar do
82
84
 
83
85
  describe 'sorting date / time events' do
84
86
  let(:source) { File.read File.join(File.dirname(__FILE__), 'fixtures', 'two_date_time_events.ics') }
85
- subject { Icalendar.parse(source, true).events }
87
+ subject { Icalendar::Calendar.parse(source).first.events }
86
88
 
87
89
  it 'sorts time events' do
88
90
  events = subject.sort_by(&:dtstart)
@@ -102,7 +104,7 @@ describe Icalendar do
102
104
 
103
105
  context 'strict parser' do
104
106
  let(:strict) { true }
105
- specify { expect { subject.parse }.to raise_error }
107
+ specify { expect { subject.parse }.to raise_error(NoMethodError) }
106
108
  end
107
109
 
108
110
  context 'lenient parser' do
@@ -13,7 +13,6 @@ require 'timecop'
13
13
  require 'icalendar'
14
14
 
15
15
  RSpec.configure do |config|
16
- config.treat_symbols_as_metadata_keys_with_true_values = true
17
16
  config.run_all_when_everything_filtered = true
18
17
  config.filter_run :focus
19
18
 
@@ -7,25 +7,25 @@ describe Icalendar::Timezone do
7
7
 
8
8
  context 'with both standard and daylight components' do
9
9
  before(:each) do
10
- subject.daylight { |d| d.should_receive(:valid?).and_return true }
11
- subject.standard { |s| s.should_receive(:valid?).and_return true }
10
+ subject.daylight { |d| allow(d).to receive(:valid?).and_return true }
11
+ subject.standard { |s| allow(s).to receive(:valid?).and_return true }
12
12
  end
13
13
 
14
14
  it { should be_valid }
15
15
  end
16
16
 
17
17
  context 'with only standard' do
18
- before(:each) { subject.standard { |s| s.stub(:valid?).and_return true } }
19
- it { should be_valid }
18
+ before(:each) { subject.standard { |s| allow(s).to receive(:valid?).and_return true } }
19
+ it { expect(subject).to be_valid }
20
20
  end
21
21
 
22
22
  context 'with only daylight' do
23
- before(:each) { subject.daylight { |d| d.stub(:valid?).and_return true } }
24
- it { should be_valid }
23
+ before(:each) { subject.daylight { |d| allow(d).to receive(:valid?).and_return true } }
24
+ it { expect(subject).to be_valid }
25
25
  end
26
26
 
27
27
  context 'with neither standard or daylight' do
28
28
  it { should_not be_valid }
29
29
  end
30
30
  end
31
- end
31
+ end
@@ -26,18 +26,18 @@ describe 'TZInfo::Timezone' do
26
26
  end
27
27
 
28
28
  describe 'no end transition' do
29
- let(:tz) { TZInfo::Timezone.get 'America/Cayman' }
29
+ let(:tz) { TZInfo::Timezone.get 'Asia/Shanghai' }
30
30
  let(:date) { DateTime.now }
31
31
 
32
32
  it 'only creates a standard component' do
33
33
  expect(subject.to_ical).to eq <<-EXPECTED.gsub "\n", "\r\n"
34
34
  BEGIN:VTIMEZONE
35
- TZID:America/Cayman
35
+ TZID:Asia/Shanghai
36
36
  BEGIN:STANDARD
37
- DTSTART:19120201T000711
38
- TZOFFSETFROM:-0652
39
- TZOFFSETTO:-0500
40
- TZNAME:EST
37
+ DTSTART:19910914T230000
38
+ TZOFFSETFROM:+0900
39
+ TZOFFSETTO:+0800
40
+ TZNAME:CST
41
41
  END:STANDARD
42
42
  END:VTIMEZONE
43
43
  EXPECTED
@@ -0,0 +1,41 @@
1
+ require 'spec_helper'
2
+
3
+ describe Icalendar::Values::DateOrDateTime do
4
+
5
+ subject { described_class.new value, params }
6
+ let(:params) { {} }
7
+
8
+ describe '#call' do
9
+ context 'DateTime value' do
10
+ let(:value) { '20140209T194355Z' }
11
+
12
+ it 'returns a DateTime object' do
13
+ expect(subject.call).to be_a_kind_of(Icalendar::Values::DateTime)
14
+ end
15
+
16
+ it 'has the proper value' do
17
+ expect(subject.call.value).to eq DateTime.new(2014, 2, 9, 19, 43, 55)
18
+ end
19
+ end
20
+
21
+ context 'Date value' do
22
+ let(:value) { '20140209' }
23
+
24
+ it 'returns a Date object' do
25
+ expect(subject.call).to be_a_kind_of(Icalendar::Values::Date)
26
+ end
27
+
28
+ it 'has the proper value' do
29
+ expect(subject.call.value).to eq Date.new(2014, 2, 9)
30
+ end
31
+ end
32
+
33
+ context 'unparseable date' do
34
+ let(:value) { '99999999' }
35
+
36
+ it 'raises an error including the unparseable time' do
37
+ expect { subject.call }.to raise_error(ArgumentError, %r{Failed to parse \"#{value}\"})
38
+ end
39
+ end
40
+ end
41
+ end
@@ -68,5 +68,11 @@ describe Icalendar::Values::Text do
68
68
  expect(subject.params_ical).to eq %(;PARAM="Hello, 'World'",GoodbyeMoon)
69
69
  end
70
70
  end
71
+ context 'nil value' do
72
+ let(:param_value) { nil }
73
+ it 'trats nil as blank' do
74
+ expect(subject.params_ical).to eq %(;PARAM=)
75
+ end
76
+ end
71
77
  end
72
78
  end
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: 2.3.0
4
+ version: 2.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ryan Ahearn
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-04-26 00:00:00.000000000 Z
11
+ date: 2016-07-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -114,14 +114,14 @@ dependencies:
114
114
  requirements:
115
115
  - - "~>"
116
116
  - !ruby/object:Gem::Version
117
- version: '2.14'
117
+ version: '3.0'
118
118
  type: :development
119
119
  prerelease: false
120
120
  version_requirements: !ruby/object:Gem::Requirement
121
121
  requirements:
122
122
  - - "~>"
123
123
  - !ruby/object:Gem::Version
124
- version: '2.14'
124
+ version: '3.0'
125
125
  - !ruby/object:Gem::Dependency
126
126
  name: simplecov
127
127
  requirement: !ruby/object:Gem::Requirement
@@ -177,6 +177,7 @@ files:
177
177
  - lib/icalendar/values/boolean.rb
178
178
  - lib/icalendar/values/cal_address.rb
179
179
  - lib/icalendar/values/date.rb
180
+ - lib/icalendar/values/date_or_date_time.rb
180
181
  - lib/icalendar/values/date_time.rb
181
182
  - lib/icalendar/values/duration.rb
182
183
  - lib/icalendar/values/float.rb
@@ -193,6 +194,7 @@ files:
193
194
  - spec/calendar_spec.rb
194
195
  - spec/downcased_hash_spec.rb
195
196
  - spec/event_spec.rb
197
+ - spec/fixtures/event.ics
196
198
  - spec/fixtures/nondefault_values.ics
197
199
  - spec/fixtures/nonstandard.ics
198
200
  - spec/fixtures/recurrence.ics
@@ -211,6 +213,7 @@ files:
211
213
  - spec/timezone_spec.rb
212
214
  - spec/todo_spec.rb
213
215
  - spec/tzinfo_spec.rb
216
+ - spec/values/date_or_date_time_spec.rb
214
217
  - spec/values/date_time_spec.rb
215
218
  - spec/values/duration_spec.rb
216
219
  - spec/values/period_spec.rb
@@ -242,7 +245,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
242
245
  version: '0'
243
246
  requirements: []
244
247
  rubyforge_project:
245
- rubygems_version: 2.4.5
248
+ rubygems_version: 2.5.1
246
249
  signing_key:
247
250
  specification_version: 4
248
251
  summary: A ruby implementation of the iCalendar specification (RFC-5545).
@@ -251,6 +254,7 @@ test_files:
251
254
  - spec/calendar_spec.rb
252
255
  - spec/downcased_hash_spec.rb
253
256
  - spec/event_spec.rb
257
+ - spec/fixtures/event.ics
254
258
  - spec/fixtures/nondefault_values.ics
255
259
  - spec/fixtures/nonstandard.ics
256
260
  - spec/fixtures/recurrence.ics
@@ -269,6 +273,7 @@ test_files:
269
273
  - spec/timezone_spec.rb
270
274
  - spec/todo_spec.rb
271
275
  - spec/tzinfo_spec.rb
276
+ - spec/values/date_or_date_time_spec.rb
272
277
  - spec/values/date_time_spec.rb
273
278
  - spec/values/duration_spec.rb
274
279
  - spec/values/period_spec.rb