icalendar 2.2.2 → 2.3.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 144e7b7601ab22b7de6b32db6da271ca7f2e487d
4
- data.tar.gz: 36fe6e7a0bc064ff6d989dbbc40b56a87c42508d
3
+ metadata.gz: 3ed623b569af5c7f68aa5917f19acedb5a11f7e8
4
+ data.tar.gz: 3293337f990892e2c07cc9c8d23f5bf4b86e5a37
5
5
  SHA512:
6
- metadata.gz: 053e06312ce27ff2b6e964396171c15b3808a4954f9dc42330d6febde5d0e5339144fe5521088f446db652fa6f12a61718f7585b1025f31f26ca21cd95f1cbef
7
- data.tar.gz: 4a22eaa229789f42b8584a1a8bc707846ee4f24b0ee34851583fc8c0f2c2fdcbf4aacf34c4fdab9dc82183a64ca8cd51ed9b899837bf26a2cae5e8f54b6a5fd4
6
+ metadata.gz: 4a95ab23132eeffa95b42b1c2da033aa46b92775caf43928458ffd048685f66095855235b1b951f1bb18a1a33b7fb817cc1e4fc3f1b4917a277a3f18306dc592
7
+ data.tar.gz: 8d77c499752d3f589b9dca961d61df24f9b67a89f4764c462e71bcedd313a0f8d3b1aace26849bbe183a415235e4c6b159b8ea4b194b37cf9b202a73f001c568
@@ -1,3 +1,9 @@
1
+ === 2.3.0 2015-04-26
2
+ * fix value parameter for properties with multiple values
3
+ * fix error when assigning Icalendar::Values::Array to a component
4
+ * Fall back to Icalendar::Values::Date if Icalendar::Values::DateTime is not given a properly formatted value
5
+ * Downcase the keys in ical_params to ensure we aren't assigning both tzid and TZID
6
+
1
7
  === 2.2.2 2014-12-27
2
8
  * add a `has_#{component}?` method for testing if component exists - John Hope
3
9
  * add documentation & tests for organizer attribute - Ben Walding
@@ -0,0 +1,40 @@
1
+ require 'delegate'
2
+
3
+ module Icalendar
4
+ class DowncasedHash < ::SimpleDelegator
5
+
6
+ def initialize(base)
7
+ super Hash.new
8
+ base.each do |key, value|
9
+ self[key] = value
10
+ end
11
+ end
12
+
13
+ def []=(key, value)
14
+ __getobj__[key.to_s.downcase] = value
15
+ end
16
+
17
+ def [](key)
18
+ __getobj__[key.to_s.downcase]
19
+ end
20
+
21
+ def has_key?(key)
22
+ __getobj__.has_key? key.to_s.downcase
23
+ end
24
+ alias_method :include?, :has_key?
25
+ alias_method :member?, :has_key?
26
+
27
+ def delete(key, &block)
28
+ __getobj__.delete key.to_s.downcase, &block
29
+ end
30
+ end
31
+
32
+ def self.DowncasedHash(base)
33
+ case base
34
+ when Icalendar::DowncasedHash then base
35
+ when Hash then Icalendar::DowncasedHash.new(base)
36
+ else
37
+ fail ArgumentError
38
+ end
39
+ end
40
+ end
@@ -62,7 +62,7 @@ module Icalendar
62
62
  klass ||= Icalendar.const_get singular_name.capitalize
63
63
  klass.new
64
64
  rescue NameError => ne
65
- puts "WARN: #{ne.message}"
65
+ Icalendar.logger.warn ne.message
66
66
  Component.new singular_name
67
67
  end
68
68
  end
@@ -32,23 +32,7 @@ module Icalendar
32
32
 
33
33
  def parse_property(component, fields = nil)
34
34
  fields = next_fields if fields.nil?
35
- klass = component.class.default_property_types[fields[:name]]
36
- if !fields[:params]['value'].nil?
37
- klass_name = fields[:params].delete('value').first
38
- unless klass_name.upcase == klass.value_type
39
- klass_name = klass_name.downcase.gsub(/(?:\A|-)(.)/) { |m| m[-1].upcase }
40
- klass = Icalendar::Values.const_get klass_name if Icalendar::Values.const_defined?(klass_name)
41
- end
42
- end
43
- if klass.value_type != 'RECUR' && fields[:value] =~ /(?<!\\)([,;])/
44
- delimiter = $1
45
- prop_value = Icalendar::Values::Array.new fields[:value].split(/(?<!\\)[;,]/),
46
- klass,
47
- fields[:params],
48
- delimiter: delimiter
49
- else
50
- prop_value = klass.new fields[:value], fields[:params]
51
- end
35
+ prop_value = wrap_property_value component, fields
52
36
  prop_name = %w(class method).include?(fields[:name]) ? "ip_#{fields[:name]}" : fields[:name]
53
37
  begin
54
38
  method_name = if component.class.multiple_properties.include? prop_name
@@ -68,6 +52,35 @@ module Icalendar
68
52
  end
69
53
  end
70
54
 
55
+ def wrap_property_value(component, fields)
56
+ klass = get_wrapper_class component, fields
57
+ if klass.value_type != 'RECUR' && fields[:value] =~ /(?<!\\)([,;])/
58
+ delimiter = $1
59
+ Icalendar::Values::Array.new fields[:value].split(/(?<!\\)[;,]/),
60
+ klass,
61
+ fields[:params],
62
+ delimiter: delimiter
63
+ else
64
+ klass.new fields[:value], fields[:params]
65
+ end
66
+ rescue Icalendar::Values::DateTime::FormatError => fe
67
+ raise fe if strict?
68
+ fields[:params]['value'] = ['DATE']
69
+ retry
70
+ end
71
+
72
+ def get_wrapper_class(component, fields)
73
+ klass = component.class.default_property_types[fields[:name]]
74
+ if !fields[:params]['value'].nil?
75
+ klass_name = fields[:params].delete('value').first
76
+ unless klass_name.upcase == klass.value_type
77
+ klass_name = klass_name.downcase.gsub(/(?:\A|-)(.)/) { |m| m[-1].upcase }
78
+ klass = Icalendar::Values.const_get klass_name if Icalendar::Values.const_defined?(klass_name)
79
+ end
80
+ end
81
+ klass
82
+ end
83
+
71
84
  def strict?
72
85
  !!@strict
73
86
  end
@@ -1,4 +1,5 @@
1
1
  require 'delegate'
2
+ require 'icalendar/downcased_hash'
2
3
 
3
4
  module Icalendar
4
5
 
@@ -7,12 +8,12 @@ module Icalendar
7
8
  attr_accessor :ical_params
8
9
 
9
10
  def initialize(value, params = {})
10
- @ical_params = params.dup
11
+ @ical_params = Icalendar::DowncasedHash(params)
11
12
  super value
12
13
  end
13
14
 
14
15
  def ical_param(key, value)
15
- @ical_params[key.to_s] = value
16
+ @ical_params[key] = value
16
17
  end
17
18
 
18
19
  def value
@@ -20,7 +21,7 @@ module Icalendar
20
21
  end
21
22
 
22
23
  def to_ical(default_type)
23
- ical_param 'value', self.class.value_type if needs_value_type?(default_type)
24
+ ical_param 'value', self.value_type if needs_value_type?(default_type)
24
25
  "#{params_ical}:#{value_ical}"
25
26
  end
26
27
 
@@ -34,6 +35,10 @@ module Icalendar
34
35
  name.gsub(/\A.*::/, '').gsub(/(?<!\A)[A-Z]/, '-\0').upcase
35
36
  end
36
37
 
38
+ def value_type
39
+ self.class.value_type
40
+ end
41
+
37
42
  private
38
43
 
39
44
  def needs_value_type?(default_type)
@@ -77,4 +82,4 @@ require_relative 'values/uri'
77
82
  require_relative 'values/utc_offset'
78
83
 
79
84
  # further refine above classes
80
- require_relative 'values/cal_address'
85
+ require_relative 'values/cal_address'
@@ -3,10 +3,18 @@ module Icalendar
3
3
 
4
4
  class Array < Value
5
5
 
6
+ attr_reader :value_delimiter
7
+
6
8
  def initialize(value, klass, params = {}, options = {})
7
9
  @value_delimiter = options[:delimiter] || ','
8
10
  mapped = if value.is_a? ::Array
9
- value.map { |v| klass.new v, params }
11
+ value.map do |v|
12
+ if v.is_a? Icalendar::Values::Array
13
+ Icalendar::Values::Array.new v.value, klass, v.ical_params, delimiter: v.value_delimiter
14
+ else
15
+ klass.new v, params
16
+ end
17
+ end
10
18
  else
11
19
  [klass.new(value, params)]
12
20
  end
@@ -23,7 +31,7 @@ module Icalendar
23
31
  def value_ical
24
32
  value.map do |v|
25
33
  v.value_ical
26
- end.join @value_delimiter
34
+ end.join value_delimiter
27
35
  end
28
36
 
29
37
  def valid?
@@ -31,6 +39,10 @@ module Icalendar
31
39
  !value.all? { |v| v.class == klass }
32
40
  end
33
41
 
42
+ def value_type
43
+ value.first.value_type
44
+ end
45
+
34
46
  private
35
47
 
36
48
  def needs_value_type?(default_type)
@@ -40,4 +52,4 @@ module Icalendar
40
52
  end
41
53
 
42
54
  end
43
- end
55
+ end
@@ -16,7 +16,7 @@ module Icalendar
16
16
  begin
17
17
  parsed_date = ::DateTime.strptime(value, FORMAT)
18
18
  rescue ArgumentError => e
19
- raise ArgumentError.new("Failed to parse \"#{value}\" - #{e.message}")
19
+ raise FormatError.new("Failed to parse \"#{value}\" - #{e.message}")
20
20
  end
21
21
 
22
22
  super parsed_date, params
@@ -43,6 +43,8 @@ module Icalendar
43
43
  end
44
44
  end
45
45
 
46
+ class FormatError < ArgumentError
47
+ end
46
48
  end
47
49
 
48
50
  end
@@ -14,6 +14,7 @@ module Icalendar
14
14
  attr_reader :tz_utc
15
15
 
16
16
  def initialize(value, params = {})
17
+ params = Icalendar::DowncasedHash(params)
17
18
  @tz_utc = params['tzid'] == 'UTC'
18
19
 
19
20
  if defined?(ActiveSupport::TimeZone) && defined?(ActiveSupportTimeWithZoneAdapter) && !params['tzid'].nil?
@@ -22,7 +23,7 @@ module Icalendar
22
23
  value = ActiveSupportTimeWithZoneAdapter.new nil, zone, value unless zone.nil?
23
24
  super value, params
24
25
  else
25
- super
26
+ super value, params
26
27
  end
27
28
  end
28
29
 
@@ -1,5 +1,5 @@
1
1
  module Icalendar
2
2
 
3
- VERSION = '2.2.2'
3
+ VERSION = '2.3.0'
4
4
 
5
5
  end
@@ -0,0 +1,52 @@
1
+ require 'spec_helper'
2
+
3
+ describe Icalendar::DowncasedHash do
4
+
5
+ subject { described_class.new base }
6
+ let(:base) { {'hello' => 'world'} }
7
+
8
+ describe '#[]=' do
9
+ it 'sets a new value' do
10
+ subject['FOO'] = 'bar'
11
+ expect(subject['foo']).to eq 'bar'
12
+ end
13
+ end
14
+
15
+ describe '#[]' do
16
+ it 'gets an already set value' do
17
+ subject['foo'] = 'bar'
18
+ expect(subject['FOO']).to eq 'bar'
19
+ end
20
+ end
21
+
22
+ describe '#has_key?' do
23
+ it 'correctly identifies keys in the hash' do
24
+ expect(subject.has_key? 'hello').to be true
25
+ expect(subject.has_key? 'HELLO').to be true
26
+ end
27
+ end
28
+
29
+ describe '#delete' do
30
+ context 'no block' do
31
+ it 'removes the key' do
32
+ subject.delete 'HELLO'
33
+ expect(subject.has_key? 'hello').to be false
34
+ end
35
+ end
36
+ context 'with a block' do
37
+ it 'calls the block when the key is not found' do
38
+ expect { |b| subject.delete 'nokey', &b }.to yield_with_args('nokey')
39
+ end
40
+ end
41
+ end
42
+
43
+ describe 'DowncasedHash()' do
44
+ it 'returns self when passed an DowncasedHash' do
45
+ expect(Icalendar::DowncasedHash(subject)).to be subject
46
+ end
47
+
48
+ it 'wraps a hash in an downcased hash' do
49
+ expect(Icalendar::DowncasedHash(base)).to be_kind_of Icalendar::DowncasedHash
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,18 @@
1
+ BEGIN:VCALENDAR
2
+ VERSION:2.0
3
+ PRODID:bsprodidfortestabc123
4
+ CALSCALE:GREGORIAN
5
+ BEGIN:VEVENT
6
+ DTSTAMP:20050118T211523Z
7
+ UID:bsuidfortestabc123
8
+ DTSTART;VALUE=DATE:20050323
9
+ DTEND;VALUE=DATE:20050323
10
+ CLASS:PRIVATE
11
+ ORGANIZER:mailto:joebob@random.net
12
+ PRIORITY:2
13
+ SUMMARY:This is a really long summary to test the method of unfolding lines
14
+ \, so I'm just going to make it a whole bunch of lines.
15
+ EXDATE;VALUE=DATE:20120323,20130323
16
+ RRULE:FREQ=YEARLY
17
+ END:VEVENT
18
+ END:VCALENDAR
@@ -0,0 +1,21 @@
1
+ BEGIN:VCALENDAR
2
+ VERSION:2.0
3
+ PRODID:bsprodidfortestabc123
4
+ CALSCALE:GREGORIAN
5
+ BEGIN:VEVENT
6
+ DTSTAMP:20050118T211523Z
7
+ UID:bsuidfortestabc123
8
+ DTSTART:20050120
9
+ DTEND;TZID=US-Mountain:20050120T184500
10
+ CLASS:PRIVATE
11
+ GEO:37.386013;-122.0829322
12
+ ORGANIZER:mailto:joebob@random.net
13
+ PRIORITY:2
14
+ SUMMARY:This is a really long summary to test the method of unfolding lines
15
+ \, so I'm just going to make it a whole bunch of lines.
16
+ ATTACH:http://bush.sucks.org/impeach/him.rhtml
17
+ ATTACH:http://corporations-dominate.existence.net/why.rhtml
18
+ RDATE;TZID=US-Mountain:20050121T170000,20050122T170000
19
+ X-TEST-COMPONENT;QTEST="Hello, World":Shouldn't double double quotes
20
+ END:VEVENT
21
+ END:VCALENDAR
@@ -2,29 +2,40 @@ require 'spec_helper'
2
2
 
3
3
  describe Icalendar::Parser do
4
4
  subject { described_class.new source, false }
5
+ let(:source) { File.read File.join(File.dirname(__FILE__), 'fixtures', fn) }
5
6
 
6
7
  describe '#parse' do
7
- let(:source) { File.read File.join(File.dirname(__FILE__), 'fixtures', 'single_event.ics') }
8
+ context 'single_event.ics' do
9
+ let(:fn) { 'single_event.ics' }
8
10
 
9
- it 'returns an array of calendars' do
10
- expect(subject.parse).to be_instance_of Array
11
- expect(subject.parse.count).to eq 1
12
- expect(subject.parse[0]).to be_instance_of Icalendar::Calendar
13
- end
11
+ it 'returns an array of calendars' do
12
+ expect(subject.parse).to be_instance_of Array
13
+ expect(subject.parse.count).to eq 1
14
+ expect(subject.parse[0]).to be_instance_of Icalendar::Calendar
15
+ end
14
16
 
15
- it 'properly splits multi-valued lines' do
16
- event = subject.parse.first.events.first
17
- expect(event.geo).to eq [37.386013,-122.0829322]
18
- end
17
+ it 'properly splits multi-valued lines' do
18
+ event = subject.parse.first.events.first
19
+ expect(event.geo).to eq [37.386013,-122.0829322]
20
+ end
19
21
 
20
- it 'saves params' do
21
- event = subject.parse.first.events.first
22
- expect(event.dtstart.ical_params).to eq('tzid' => ['US-Mountain'])
22
+ it 'saves params' do
23
+ event = subject.parse.first.events.first
24
+ expect(event.dtstart.ical_params).to eq('tzid' => ['US-Mountain'])
25
+ end
26
+ end
27
+ context 'recurrence.ics' do
28
+ let(:fn) { 'recurrence.ics' }
29
+ it 'correctly parses the exdate array' do
30
+ event = subject.parse.first.events.first
31
+ ics = event.to_ical
32
+ expect(ics).to match 'EXDATE;VALUE=DATE:20120323,20130323'
33
+ end
23
34
  end
24
35
  end
25
36
 
26
37
  describe '#parse with bad line' do
27
- let(:source) { File.read File.join(File.dirname(__FILE__), 'fixtures', 'single_event_bad_line.ics') }
38
+ let(:fn) { 'single_event_bad_line.ics' }
28
39
 
29
40
  it 'returns an array of calendars' do
30
41
  expect(subject.parse).to be_instance_of Array
@@ -42,4 +53,13 @@ describe Icalendar::Parser do
42
53
  expect(event.dtstart.ical_params).to eq('tzid' => ['US-Mountain'])
43
54
  end
44
55
  end
56
+
57
+ describe 'missing date value parameter' do
58
+ let(:fn) { 'single_event_bad_dtstart.ics' }
59
+
60
+ it 'falls back to date type for dtstart' do
61
+ event = subject.parse.first.events.first
62
+ expect(event.dtstart).to be_kind_of Icalendar::Values::Date
63
+ end
64
+ end
45
65
  end
@@ -8,6 +8,14 @@ describe Icalendar do
8
8
  it 'will generate the same file as is parsed' do
9
9
  expect(Icalendar.parse(source, true).to_ical).to eq source
10
10
  end
11
+
12
+ it 'array properties can be assigned to a new event' do
13
+ event = Icalendar::Event.new
14
+ parsed = Icalendar.parse source, true
15
+ event.rdate = parsed.events.first.rdate
16
+ expect(event.rdate.first).to be_kind_of Icalendar::Values::Array
17
+ expect(event.rdate.first.ical_params).to eq 'tzid' => ['US-Mountain']
18
+ end
11
19
  end
12
20
 
13
21
  describe 'timezone round trip' do
@@ -56,6 +56,15 @@ describe Icalendar::Values::DateTime do
56
56
  expect(subject.to_ical described_class).to eq ":#{value}"
57
57
  end
58
58
 
59
+ context 'manually set UTC' do
60
+ let(:value) { '20140209T194355' }
61
+ let(:params) { {'TZID' => 'UTC'} }
62
+
63
+ it 'does not add a tzid parameter, but does add a Z' do
64
+ expect(subject.to_ical described_class).to eq ":#{value}Z"
65
+ end
66
+ end
67
+
59
68
  context 'local time' do
60
69
  let(:value) { '20140209T160652' }
61
70
  let(:params) { {'tzid' => 'America/Denver'} }
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.2.2
4
+ version: 2.3.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: 2014-12-27 00:00:00.000000000 Z
11
+ date: 2015-04-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -159,6 +159,7 @@ files:
159
159
  - lib/icalendar/alarm.rb
160
160
  - lib/icalendar/calendar.rb
161
161
  - lib/icalendar/component.rb
162
+ - lib/icalendar/downcased_hash.rb
162
163
  - lib/icalendar/event.rb
163
164
  - lib/icalendar/freebusy.rb
164
165
  - lib/icalendar/has_components.rb
@@ -190,10 +191,13 @@ files:
190
191
  - lib/icalendar/version.rb
191
192
  - spec/alarm_spec.rb
192
193
  - spec/calendar_spec.rb
194
+ - spec/downcased_hash_spec.rb
193
195
  - spec/event_spec.rb
194
196
  - spec/fixtures/nondefault_values.ics
195
197
  - spec/fixtures/nonstandard.ics
198
+ - spec/fixtures/recurrence.ics
196
199
  - spec/fixtures/single_event.ics
200
+ - spec/fixtures/single_event_bad_dtstart.ics
197
201
  - spec/fixtures/single_event_bad_line.ics
198
202
  - spec/fixtures/timezone.ics
199
203
  - spec/fixtures/two_date_time_events.ics
@@ -238,17 +242,20 @@ required_rubygems_version: !ruby/object:Gem::Requirement
238
242
  version: '0'
239
243
  requirements: []
240
244
  rubyforge_project:
241
- rubygems_version: 2.2.2
245
+ rubygems_version: 2.4.5
242
246
  signing_key:
243
247
  specification_version: 4
244
248
  summary: A ruby implementation of the iCalendar specification (RFC-5545).
245
249
  test_files:
246
250
  - spec/alarm_spec.rb
247
251
  - spec/calendar_spec.rb
252
+ - spec/downcased_hash_spec.rb
248
253
  - spec/event_spec.rb
249
254
  - spec/fixtures/nondefault_values.ics
250
255
  - spec/fixtures/nonstandard.ics
256
+ - spec/fixtures/recurrence.ics
251
257
  - spec/fixtures/single_event.ics
258
+ - spec/fixtures/single_event_bad_dtstart.ics
252
259
  - spec/fixtures/single_event_bad_line.ics
253
260
  - spec/fixtures/timezone.ics
254
261
  - spec/fixtures/two_date_time_events.ics