icalendar 2.6.1 → 2.8.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
  SHA256:
3
- metadata.gz: 4fd1e657ab9c20d0576627a5d04bcc6991205bf8f086ad8150e5bcbc179f6c49
4
- data.tar.gz: 2a29f3a0da940f8c2caa83bc49e2cfad4cb2fa31b72a64c1b7f200a65571bfb9
3
+ metadata.gz: becd9ceba3342d0b602f1bb8a33858fb2176ceafd457a0fbca76d24ff7800dc7
4
+ data.tar.gz: f11054752ca18d026b1339e936ab30d4f1ff61219ecfd94d3f913b3de6b14106
5
5
  SHA512:
6
- metadata.gz: '02840f7036575111fdfce4253131e15b574a38f1f48061229863a52514d661943e119c859a983df0ce3d16818e86a34cbcd9b7e2813fb4b91e38d09d8be5e9e2'
7
- data.tar.gz: 59eb643eafb0a108da246806fe737c58e5592e0baf14af7d04ccd131ebc03e164bd3433ce2eced74e9d2054fa648a52aad64252d3eaecdc6df7a367ebbdf2107
6
+ metadata.gz: 8262dd28d571778ecc7dd638d6df2a44e4c26bea2221183d82a644c777d100a75027c83ac83fd53c801d43cd75b9521be104111900d6653119a0593a281b8203
7
+ data.tar.gz: 1dea941db6e27188b9ab0d769a0df2335fdc2bc492a4ca6339a5e4a90e0d91f9821282aaed13b2cc4f8a4921e24a3d20f14aa34fea97c50c6326d3a1dc29f3cc
@@ -0,0 +1,32 @@
1
+ name: Ruby
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+ - master
8
+ pull_request:
9
+ branches:
10
+ - main
11
+ - master
12
+
13
+ jobs:
14
+ build:
15
+ runs-on: ubuntu-latest
16
+
17
+ strategy:
18
+ matrix:
19
+ ruby:
20
+ - 2.7.6
21
+ - 3.0.4
22
+ - 3.1.2
23
+
24
+ steps:
25
+ - uses: actions/checkout@v2
26
+ - name: Set up Ruby
27
+ uses: ruby/setup-ruby@v1
28
+ with:
29
+ ruby-version: ${{ matrix.ruby }}
30
+ bundler-cache: true
31
+ - name: Run rspec tests
32
+ run: bundle exec rake spec
data/History.txt CHANGED
@@ -1,3 +1,17 @@
1
+ === 2.8.0 2022-07-10
2
+ * Fix compatibility with ActiveSupport 7 - Pat Allan
3
+ * Set default action of "DISPLAY" on alarms - Rikson
4
+ * Add license information to gemspec - Robert Reiz
5
+ * Support RFC7986 properties - Daniele Frisanco
6
+
7
+ === 2.7.1 2021-03-14
8
+ * Recover from bad line-wrapping code that splits in the middle of Unicode code points
9
+ * Add a verbose option to the Parser to quiet some of the chattier log entries
10
+
11
+ === 2.7.0 2020-09-12
12
+ * Handle custom component names, with and without X- prefix
13
+ * Fix Component lookup to avoid namespace collisions
14
+
1
15
  === 2.6.1 2019-12-07
2
16
  * Improve performance when generating large ICS files - Alex Balhatchet
3
17
 
data/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  iCalendar -- Internet calendaring, Ruby style
2
2
  ===
3
3
 
4
- [![Build Status](https://travis-ci.org/icalendar/icalendar.svg?branch=master)](https://travis-ci.org/icalendar/icalendar)
4
+ [![Build Status](https://travis-ci.com/icalendar/icalendar.svg?branch=master)](https://travis-ci.com/icalendar/icalendar)
5
5
  [![Code Climate](https://codeclimate.com/github/icalendar/icalendar.png)](https://codeclimate.com/github/icalendar/icalendar)
6
6
 
7
7
  <http://github.com/icalendar/icalendar>
@@ -115,7 +115,7 @@ cal.event do |e|
115
115
  a.attendee = %w(mailto:me@my-domain.com mailto:me-too@my-domain.com) # one or more email recipients (required)
116
116
  a.append_attendee "mailto:me-three@my-domain.com"
117
117
  a.trigger = "-PT15M" # 15 minutes before
118
- a.append_attach Icalendar::Values::Uri.new "ftp://host.com/novo-procs/felizano.exe", "fmttype" => "application/binary" # email attachments (optional)
118
+ a.append_attach Icalendar::Values::Uri.new("ftp://host.com/novo-procs/felizano.exe", "fmttype" => "application/binary") # email attachments (optional)
119
119
  end
120
120
 
121
121
  e.alarm do |a|
@@ -220,7 +220,8 @@ end
220
220
  iCalendar has some basic support for creating VTIMEZONE blocks from timezone information pulled from `tzinfo`.
221
221
  You must require `tzinfo` support manually to take advantage.
222
222
 
223
- iCalendar has been tested and works with `tzinfo` versions 0.3 and 1.x
223
+ iCalendar has been tested and works with `tzinfo` versions 0.3, 1.x, and 2.x. The `tzinfo-data` gem may also
224
+ be required depending on your version of `tzinfo` and potentially your operating system.
224
225
 
225
226
  #### Example ####
226
227
 
data/icalendar.gemspec CHANGED
@@ -6,6 +6,7 @@ Gem::Specification.new do |s|
6
6
 
7
7
  s.name = "icalendar"
8
8
  s.version = Icalendar::VERSION
9
+ s.licenses = ['BSD-2-Clause', 'GPL-3.0-only', 'icalendar']
9
10
 
10
11
  s.homepage = "https://github.com/icalendar/icalendar"
11
12
  s.platform = Gem::Platform::RUBY
@@ -28,20 +29,19 @@ ActiveSupport is required for TimeWithZone support, but not required for general
28
29
 
29
30
  s.add_dependency 'ice_cube', '~> 0.16'
30
31
 
31
- s.add_development_dependency 'rake', '~> 12.0'
32
+ s.add_development_dependency 'rake', '~> 13.0'
32
33
  s.add_development_dependency 'bundler', '~> 2.0'
33
34
 
34
35
  # test with all groups of tzinfo dependencies
35
36
  # tzinfo 2.x
36
37
  # s.add_development_dependency 'tzinfo', '~> 2.0'
37
- # s.add_development_dependency 'tzinfo-data', '~> 1.2018'
38
+ # s.add_development_dependency 'tzinfo-data', '~> 1.2020'
38
39
  # tzinfo 1.x
39
- s.add_development_dependency 'activesupport', '~> 5.2'
40
- s.add_development_dependency 'i18n', '~> 1.1'
40
+ s.add_development_dependency 'activesupport', '~> 6.0'
41
+ s.add_development_dependency 'i18n', '~> 1.8'
41
42
  s.add_development_dependency 'tzinfo', '~> 1.2'
42
- s.add_development_dependency 'tzinfo-data', '~> 1.2018'
43
+ s.add_development_dependency 'tzinfo-data', '~> 1.2020'
43
44
  # tzinfo 0.x
44
- # s.add_development_dependency 'i18n', '~> 0.7'
45
45
  # s.add_development_dependency 'tzinfo', '~> 0.3'
46
46
  # end tzinfo
47
47
 
@@ -22,6 +22,7 @@ module Icalendar
22
22
 
23
23
  def initialize
24
24
  super 'alarm'
25
+ self.action = 'DISPLAY'
25
26
  end
26
27
 
27
28
  def valid?(strict = false)
@@ -5,6 +5,16 @@ module Icalendar
5
5
  required_property :prodid
6
6
  optional_single_property :calscale
7
7
  optional_single_property :ip_method
8
+ optional_property :ip_name
9
+ optional_property :description
10
+ optional_single_property :uid
11
+ optional_single_property :last_modified, Icalendar::Values::DateTime, true
12
+ optional_single_property :url, Icalendar::Values::Uri, true
13
+ optional_property :categories
14
+ optional_single_property :refresh_interval, Icalendar::Values::Duration, true
15
+ optional_single_property :source, Icalendar::Values::Uri, true
16
+ optional_single_property :color
17
+ optional_property :image, Icalendar::Values::Uri, false, true
8
18
 
9
19
  component :timezone, :tzid
10
20
  component :event
@@ -11,9 +11,10 @@ module Icalendar
11
11
  attr_accessor :parent
12
12
 
13
13
  def self.parse(source)
14
- parser = Parser.new(source)
15
- parser.component_class = self
16
- parser.parse
14
+ _parse source
15
+ rescue ArgumentError
16
+ source.rewind if source.respond_to?(:rewind)
17
+ _parse Parser.clean_bad_wrapping(source)
17
18
  end
18
19
 
19
20
  def initialize(name, ical_name = nil)
@@ -101,6 +102,14 @@ module Icalendar
101
102
  end
102
103
  collection.empty? ? nil : collection.join.chomp("\r\n")
103
104
  end
105
+
106
+ class << self
107
+ private def _parse(source)
108
+ parser = Parser.new(source)
109
+ parser.component_class = self
110
+ parser.parse
111
+ end
112
+ end
104
113
  end
105
114
 
106
115
  end
@@ -12,6 +12,7 @@ module Icalendar
12
12
  mutually_exclusive_properties :dtend, :duration
13
13
 
14
14
  optional_single_property :ip_class
15
+ optional_single_property :color
15
16
  optional_single_property :created, Icalendar::Values::DateTime
16
17
  optional_single_property :description
17
18
  optional_single_property :geo, Icalendar::Values::Float
@@ -37,6 +38,8 @@ module Icalendar
37
38
  optional_property :related_to
38
39
  optional_property :resources
39
40
  optional_property :rdate, Icalendar::Values::DateTime
41
+ optional_property :conference, Icalendar::Values::Uri, false, true
42
+ optional_property :image, Icalendar::Values::Uri, false, true
40
43
 
41
44
  component :alarm, false
42
45
 
@@ -21,21 +21,33 @@ module Icalendar
21
21
  c
22
22
  end
23
23
 
24
+ def add_custom_component(component_name, c)
25
+ c.parent = self
26
+ yield c if block_given?
27
+ (custom_components[component_name.downcase.gsub("-", "_")] ||= []) << c
28
+ c
29
+ end
30
+
31
+ def custom_component(component_name)
32
+ custom_components[component_name.downcase.gsub("-", "_")] || []
33
+ end
34
+
24
35
  def method_missing(method, *args, &block)
25
36
  method_name = method.to_s
26
37
  if method_name =~ /^add_(x_\w+)$/
27
38
  component_name = $1
28
39
  custom = args.first || Component.new(component_name, component_name.upcase)
29
- (custom_components[component_name] ||= []) << custom
30
- yield custom if block_given?
31
- custom
40
+ add_custom_component(component_name, custom, &block)
41
+ elsif method_name =~ /^x_/ && custom_component(method_name).size > 0
42
+ custom_component method_name
32
43
  else
33
44
  super
34
45
  end
35
46
  end
36
47
 
37
48
  def respond_to_missing?(method_name, include_private = false)
38
- method_name.to_s.start_with?('add_x_') || super
49
+ string_method = method_name.to_s
50
+ string_method.start_with?('add_x_') || custom_component(string_method).size > 0 || super
39
51
  end
40
52
 
41
53
  module ClassMethods
@@ -104,29 +104,29 @@ module Icalendar
104
104
  def required_property(prop, klass = Icalendar::Values::Text, validator = nil)
105
105
  validator ||= ->(component, value) { !value.nil? }
106
106
  self.required_properties[prop] = validator
107
- single_property prop, klass
107
+ single_property prop, klass, false
108
108
  end
109
109
 
110
110
  def required_multi_property(prop, klass = Icalendar::Values::Text, validator = nil)
111
111
  validator ||= ->(component, value) { !value.compact.empty? }
112
112
  self.required_properties[prop] = validator
113
- multi_property prop, klass
113
+ multi_property prop, klass, false
114
114
  end
115
115
 
116
- def optional_single_property(prop, klass = Icalendar::Values::Text)
117
- single_property prop, klass
116
+ def optional_single_property(prop, klass = Icalendar::Values::Text, new_property = false)
117
+ single_property prop, klass, new_property
118
118
  end
119
119
 
120
120
  def mutually_exclusive_properties(*properties)
121
121
  self.mutex_properties << properties
122
122
  end
123
123
 
124
- def optional_property(prop, klass = Icalendar::Values::Text, suggested_single = false)
124
+ def optional_property(prop, klass = Icalendar::Values::Text, suggested_single = false, new_property = false)
125
125
  self.suggested_single_properties << prop if suggested_single
126
- multi_property prop, klass
126
+ multi_property prop, klass, new_property
127
127
  end
128
128
 
129
- def single_property(prop, klass)
129
+ def single_property(prop, klass, new_property)
130
130
  self.single_properties << prop.to_s
131
131
  self.default_property_types[prop.to_s] = klass
132
132
 
@@ -135,17 +135,17 @@ module Icalendar
135
135
  end
136
136
 
137
137
  define_method "#{prop}=" do |value|
138
- instance_variable_set "@#{prop}", map_property_value(value, klass, false)
138
+ instance_variable_set "@#{prop}", map_property_value(value, klass, false, new_property)
139
139
  end
140
140
  end
141
141
 
142
- def multi_property(prop, klass)
142
+ def multi_property(prop, klass, new_property)
143
143
  self.multiple_properties << prop.to_s
144
144
  self.default_property_types[prop.to_s] = klass
145
145
  property_var = "@#{prop}"
146
146
 
147
147
  define_method "#{prop}=" do |value|
148
- mapped = map_property_value value, klass, true
148
+ mapped = map_property_value value, klass, true, new_property
149
149
  if mapped.is_a? Icalendar::Values::Array
150
150
  instance_variable_set property_var, mapped.to_a.compact
151
151
  else
@@ -162,20 +162,24 @@ module Icalendar
162
162
  end
163
163
 
164
164
  define_method "append_#{prop}" do |value|
165
- send(prop) << map_property_value(value, klass, true)
165
+ send(prop) << map_property_value(value, klass, true, new_property)
166
166
  end
167
167
  end
168
168
  end
169
169
 
170
170
  private
171
171
 
172
- def map_property_value(value, klass, multi_valued)
172
+ def map_property_value(value, klass, multi_valued, new_property)
173
+ params = {}
174
+ if new_property
175
+ params.merge!('VALUE': klass.value_type)
176
+ end
173
177
  if value.nil? || value.is_a?(Icalendar::Value)
174
178
  value
175
179
  elsif value.is_a? ::Array
176
- Icalendar::Values::Array.new value, klass, {}, {delimiter: (multi_valued ? ',' : ';')}
180
+ Icalendar::Values::Array.new value, klass, params, {delimiter: (multi_valued ? ',' : ';')}
177
181
  else
178
- klass.new value
182
+ klass.new value, params
179
183
  end
180
184
  end
181
185
 
@@ -6,6 +6,7 @@ module Icalendar
6
6
  required_property :uid
7
7
 
8
8
  optional_single_property :ip_class
9
+ optional_single_property :color
9
10
  optional_single_property :created, Icalendar::Values::DateTime
10
11
  optional_single_property :dtstart, Icalendar::Values::DateTime
11
12
  optional_single_property :last_modified, Icalendar::Values::DateTime
@@ -27,6 +28,7 @@ module Icalendar
27
28
  optional_property :request_status
28
29
  optional_property :related_to
29
30
  optional_property :rdate, Icalendar::Values::DateTime
31
+ optional_property :image, Icalendar::Values::Uri, false, true
30
32
 
31
33
  def initialize
32
34
  super 'journal'
@@ -4,9 +4,24 @@ module Icalendar
4
4
 
5
5
  class Parser
6
6
  attr_writer :component_class
7
- attr_reader :source, :strict, :timezone_store
7
+ attr_reader :source, :strict, :timezone_store, :verbose
8
8
 
9
- def initialize(source, strict = false)
9
+ def self.clean_bad_wrapping(source)
10
+ content = if source.respond_to? :read
11
+ source.read
12
+ elsif source.respond_to? :to_s
13
+ source.to_s
14
+ else
15
+ msg = 'Icalendar::Parser.clean_bad_wrapping must be called with a String or IO object'
16
+ Icalendar.fatal msg
17
+ fail ArgumentError, msg
18
+ end
19
+ encoding = content.encoding
20
+ content.force_encoding(Encoding::ASCII_8BIT)
21
+ content.gsub(/\r?\n[ \t]/, "").force_encoding(encoding)
22
+ end
23
+
24
+ def initialize(source, strict = false, verbose = false)
10
25
  if source.respond_to? :gets
11
26
  @source = source
12
27
  elsif source.respond_to? :to_s
@@ -18,6 +33,7 @@ module Icalendar
18
33
  end
19
34
  read_in_data
20
35
  @strict = strict
36
+ @verbose = verbose
21
37
  @timezone_store = TimezoneStore.new
22
38
  end
23
39
 
@@ -34,7 +50,7 @@ module Icalendar
34
50
 
35
51
  def parse_property(component, fields = nil)
36
52
  fields = next_fields if fields.nil?
37
- prop_name = %w(class method).include?(fields[:name]) ? "ip_#{fields[:name]}" : fields[:name]
53
+ prop_name = %w(class method name).include?(fields[:name]) ? "ip_#{fields[:name]}" : fields[:name]
38
54
  multi_property = component.class.multiple_properties.include? prop_name
39
55
  prop_value = wrap_property_value component, fields, multi_property
40
56
  begin
@@ -49,7 +65,7 @@ module Icalendar
49
65
  Icalendar.logger.error "No method \"#{method_name}\" for component #{component}"
50
66
  raise nme
51
67
  else
52
- Icalendar.logger.warn "No method \"#{method_name}\" for component #{component}. Appending to custom."
68
+ Icalendar.logger.warn "No method \"#{method_name}\" for component #{component}. Appending to custom." if verbose?
53
69
  component.append_custom_property prop_name, prop_value
54
70
  end
55
71
  end
@@ -82,8 +98,8 @@ module Icalendar
82
98
  if !fields[:params]['value'].nil?
83
99
  klass_name = fields[:params].delete('value').first
84
100
  unless klass_name.upcase == klass.value_type
85
- klass_name = klass_name.downcase.gsub(/(?:\A|-)(.)/) { |m| m[-1].upcase }
86
- klass = Icalendar::Values.const_get klass_name if Icalendar::Values.const_defined?(klass_name)
101
+ klass_name = "Icalendar::Values::#{klass_name.downcase.gsub(/(?:\A|-)(.)/) { |m| m[-1].upcase }}"
102
+ klass = Object.const_get klass_name if Object.const_defined?(klass_name)
87
103
  end
88
104
  end
89
105
  klass
@@ -93,6 +109,10 @@ module Icalendar
93
109
  !!@strict
94
110
  end
95
111
 
112
+ def verbose?
113
+ @verbose
114
+ end
115
+
96
116
  private
97
117
 
98
118
  def component_class
@@ -106,14 +126,14 @@ module Icalendar
106
126
  timezone_store.store(component) if klass_name == 'Timezone'
107
127
  break
108
128
  elsif fields[:name] == 'begin'
109
- klass_name = fields[:value].gsub(/\AV/, '').downcase.capitalize
129
+ klass_name = fields[:value].gsub(/\AV/, '').gsub("-", "_").downcase.capitalize
110
130
  Icalendar.logger.debug "Adding component #{klass_name}"
111
- if Icalendar.const_defined? klass_name
112
- component.add_component parse_component(Icalendar.const_get(klass_name).new)
113
- elsif Icalendar::Timezone.const_defined? klass_name
114
- component.add_component parse_component(Icalendar::Timezone.const_get(klass_name).new)
131
+ if Object.const_defined? "Icalendar::#{klass_name}"
132
+ component.add_component parse_component(Object.const_get("Icalendar::#{klass_name}").new)
133
+ elsif Object.const_defined? "Icalendar::Timezone::#{klass_name}"
134
+ component.add_component parse_component(Object.const_get("Icalendar::Timezone::#{klass_name}").new)
115
135
  else
116
- component.add_component parse_component(Component.new klass_name.downcase, fields[:value])
136
+ component.add_custom_component klass_name, parse_component(Component.new klass_name.downcase, fields[:value])
117
137
  end
118
138
  else
119
139
  parse_property component, fields
@@ -12,6 +12,7 @@ module Icalendar
12
12
  mutually_exclusive_properties :due, :duration
13
13
 
14
14
  optional_single_property :ip_class
15
+ optional_single_property :color
15
16
  optional_single_property :completed, Icalendar::Values::DateTime
16
17
  optional_single_property :created, Icalendar::Values::DateTime
17
18
  optional_single_property :description
@@ -38,6 +39,8 @@ module Icalendar
38
39
  optional_property :related_to
39
40
  optional_property :resources
40
41
  optional_property :rdate, Icalendar::Values::DateTime
42
+ optional_property :conference, Icalendar::Values::Uri, false, true
43
+ optional_property :image, Icalendar::Values::Uri, false, true
41
44
 
42
45
  component :alarm, false
43
46
 
@@ -49,4 +52,4 @@ module Icalendar
49
52
 
50
53
  end
51
54
 
52
- end
55
+ end
@@ -52,7 +52,6 @@ module Icalendar
52
52
  def needs_value_type?(default_type)
53
53
  value.first.class != default_type
54
54
  end
55
-
56
55
  end
57
56
 
58
57
  end
@@ -6,6 +6,12 @@ begin
6
6
  if defined?(ActiveSupport::TimeWithZone)
7
7
  require 'icalendar/values/active_support_time_with_zone_adapter'
8
8
  end
9
+ rescue NameError
10
+ # ActiveSupport v7+ needs the base require to be run first before loading
11
+ # specific parts of it.
12
+ # https://guides.rubyonrails.org/active_support_core_extensions.html#stand-alone-active-support
13
+ require 'active_support'
14
+ retry
9
15
  rescue LoadError
10
16
  # tis ok, just a bit less fancy
11
17
  end
@@ -1,5 +1,5 @@
1
1
  module Icalendar
2
2
 
3
- VERSION = '2.6.1'
3
+ VERSION = '2.8.0'
4
4
 
5
5
  end
data/lib/icalendar.rb CHANGED
@@ -33,3 +33,4 @@ require 'icalendar/freebusy'
33
33
  require 'icalendar/timezone'
34
34
  require 'icalendar/calendar'
35
35
  require 'icalendar/parser'
36
+ require 'icalendar/version'
data/spec/alarm_spec.rb CHANGED
@@ -6,14 +6,15 @@ describe Icalendar::Alarm do
6
6
  describe '#valid?' do
7
7
  subject do
8
8
  described_class.new.tap do |a|
9
- a.action = 'AUDIO'
10
9
  a.trigger = Icalendar::Values::DateTime.new(Time.now.utc)
11
10
  end
12
11
  end
13
12
  context 'neither duration or repeat is set' do
13
+ before(:each) { subject.action = 'AUDIO' }
14
14
  it { should be_valid }
15
15
  end
16
16
  context 'both duration and repeat are set' do
17
+ before(:each) { subject.action = 'AUDIO' }
17
18
  before(:each) do
18
19
  subject.duration = 'PT15M'
19
20
  subject.repeat = 4
@@ -29,8 +30,12 @@ describe Icalendar::Alarm do
29
30
  it { should_not be_valid }
30
31
  end
31
32
 
33
+ context 'DISPLAY is set as default action' do
34
+ it 'must be DISPLAY' do
35
+ expect(subject.action).to eq 'DISPLAY'
36
+ end
37
+ end
32
38
  context 'display action' do
33
- before(:each) { subject.action = 'DISPLAY' }
34
39
  it 'requires description' do
35
40
  expect(subject).to_not be_valid
36
41
  subject.description = 'Display Text'
@@ -123,6 +123,16 @@ describe Icalendar::Calendar do
123
123
  describe '#to_ical' do
124
124
  before(:each) do
125
125
  Timecop.freeze DateTime.new(2013, 12, 26, 5, 0, 0, '+0000')
126
+ subject.ip_name = 'Company Vacation Days'
127
+ subject.description = 'The description'
128
+ subject.last_modified = "20140101T000000Z"
129
+ subject.url = 'https://example.com'
130
+ subject.color = 'red'
131
+ subject.image = 'https://example.com/image.png'
132
+ subject.uid = '5FC53010-1267-4F8E-BC28-1D7AE55A7C99'
133
+ subject.categories = 'MEETING'
134
+ subject.refresh_interval = 'P1W'
135
+ subject.source = 'https://example.com/holidays.ics'
126
136
  subject.event do |e|
127
137
  e.summary = 'An event'
128
138
  e.dtstart = "20140101T000000Z"
@@ -145,6 +155,15 @@ BEGIN:VCALENDAR
145
155
  VERSION:2.0
146
156
  PRODID:icalendar-ruby
147
157
  CALSCALE:GREGORIAN
158
+ LAST-MODIFIED;VALUE=DATE-TIME:20140101T000000Z
159
+ URL;VALUE=URI:https://example.com
160
+ REFRESH-INTERVAL;VALUE=DURATION:P1W
161
+ SOURCE;VALUE=URI:https://example.com/holidays.ics
162
+ COLOR:red
163
+ NAME:Company Vacation Days
164
+ DESCRIPTION:The description
165
+ CATEGORIES:MEETING
166
+ IMAGE;VALUE=URI:https://example.com/image.png
148
167
  BEGIN:VEVENT
149
168
  DTSTAMP:20131226T050000Z
150
169
  DTSTART:20140101T000000Z
@@ -170,4 +189,13 @@ END:VCALENDAR
170
189
  expect(subject.ip_method).to eq 'PUBLISH'
171
190
  end
172
191
  end
192
+
193
+ describe '.parse' do
194
+ let(:source) { File.read File.join(File.dirname(__FILE__), 'fixtures', 'bad_wrapping.ics') }
195
+
196
+ it 'correctly parses a bad file' do
197
+ actual = described_class.parse(source)
198
+ expect(actual[0]).to be_a(described_class)
199
+ end
200
+ end
173
201
  end
@@ -0,0 +1,14 @@
1
+ BEGIN:VCALENDAR
2
+ VERSION:2.0
3
+ PRODID:manual
4
+ CALSCALE:GREGORIAN
5
+ BEGIN:VEVENT
6
+ DTSTAMP:20200902T223352Z
7
+ UID:6e7d7fe5-6735-4cdd-bfe4-761dfcecd7a7
8
+ DTSTART;VALUE=DATE:20200902
9
+ DTEND;VALUE=DATE:20200903
10
+ DESCRIPTION:Event description that puts a UTF-8 multi-octet sequence right�
11
+ �here.
12
+ SUMMARY:UTF-8 multi-octet sequence test
13
+ END:VEVENT
14
+ END:VCALENDAR
@@ -0,0 +1,158 @@
1
+ BEGIN:VCALENDAR
2
+ PRODID:-//Atlassian Confluence//Calendar Plugin 1.0//EN
3
+ VERSION:2.0
4
+ CALSCALE:GREGORIAN
5
+ X-WR-CALNAME:Grimsell testkalender
6
+ X-WR-CALDESC:
7
+ X-WR-TIMEZONE:Europe/Stockholm
8
+ X-MIGRATED-FOR-USER-KEY:true
9
+ METHOD:PUBLISH
10
+ BEGIN:VTIMEZONE
11
+ TZID:Europe/Stockholm
12
+ TZURL:http://tzurl.org/zoneinfo/Europe/Stockholm
13
+ SEQUENCE:9206
14
+ BEGIN:DAYLIGHT
15
+ TZOFFSETFROM:+0100
16
+ TZOFFSETTO:+0200
17
+ TZNAME:CEST
18
+ DTSTART:19810329T020000
19
+ RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
20
+ END:DAYLIGHT
21
+ BEGIN:STANDARD
22
+ TZOFFSETFROM:+0200
23
+ TZOFFSETTO:+0100
24
+ TZNAME:CET
25
+ DTSTART:19961027T030000
26
+ RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
27
+ END:STANDARD
28
+ BEGIN:STANDARD
29
+ TZOFFSETFROM:+011212
30
+ TZOFFSETTO:+010014
31
+ TZNAME:SET
32
+ DTSTART:18790101T000000
33
+ RDATE:18790101T000000
34
+ END:STANDARD
35
+ BEGIN:STANDARD
36
+ TZOFFSETFROM:+010014
37
+ TZOFFSETTO:+0100
38
+ TZNAME:CET
39
+ DTSTART:19000101T000000
40
+ RDATE:19000101T000000
41
+ END:STANDARD
42
+ BEGIN:DAYLIGHT
43
+ TZOFFSETFROM:+0100
44
+ TZOFFSETTO:+0200
45
+ TZNAME:CEST
46
+ DTSTART:19160514T230000
47
+ RDATE:19160514T230000
48
+ RDATE:19800406T020000
49
+ END:DAYLIGHT
50
+ BEGIN:STANDARD
51
+ TZOFFSETFROM:+0200
52
+ TZOFFSETTO:+0100
53
+ TZNAME:CET
54
+ DTSTART:19161001T010000
55
+ RDATE:19161001T010000
56
+ RDATE:19800928T030000
57
+ RDATE:19810927T030000
58
+ RDATE:19820926T030000
59
+ RDATE:19830925T030000
60
+ RDATE:19840930T030000
61
+ RDATE:19850929T030000
62
+ RDATE:19860928T030000
63
+ RDATE:19870927T030000
64
+ RDATE:19880925T030000
65
+ RDATE:19890924T030000
66
+ RDATE:19900930T030000
67
+ RDATE:19910929T030000
68
+ RDATE:19920927T030000
69
+ RDATE:19930926T030000
70
+ RDATE:19940925T030000
71
+ RDATE:19950924T030000
72
+ END:STANDARD
73
+ BEGIN:STANDARD
74
+ TZOFFSETFROM:+0100
75
+ TZOFFSETTO:+0100
76
+ TZNAME:CET
77
+ DTSTART:19800101T000000
78
+ RDATE:19800101T000000
79
+ END:STANDARD
80
+ END:VTIMEZONE
81
+ BEGIN:VEVENT
82
+ DTSTAMP:20151203T104347Z
83
+ SUMMARY:Kaffepaus
84
+ UID:20151203T101856Z-1543254179@intranet.idainfront.se
85
+ DESCRIPTION:
86
+ ORGANIZER;X-CONFLUENCE-USER-KEY=ff80818141e4d3b60141e4d4b75700a2;CN=Magnu
87
+ s Grimsell;CUTYPE=INDIVIDUAL:mailto:magnus.grimsell@idainfront.se
88
+ CREATED:20151203T101856Z
89
+ LAST-MODIFIED:20151203T101856Z
90
+ SEQUENCE:1
91
+ X-CONFLUENCE-SUBCALENDAR-TYPE:other
92
+ TRANSP:OPAQUE
93
+ STATUS:CONFIRMED
94
+ DTSTART:20151203T090000Z
95
+ DTEND:20151203T091500Z
96
+ END:VEVENT
97
+ BEGIN:X-EVENT-SERIES
98
+ SUMMARY:iipax - Sprints
99
+ DESCRIPTION:
100
+ X-CONFLUENCE-SUBCALENDAR-TYPE:jira-agile-sprint
101
+ URL:jira://8112ef9a-343e-30e6-b469-4f993bf0371d?projectKey=II&dateFieldNa
102
+ me=sprint
103
+ END:X-EVENT-SERIES
104
+ BEGIN:VEVENT
105
+ DTSTAMP:20151203T104348Z
106
+ DTSTART;TZID=Europe/Stockholm:20130115T170800
107
+ DTEND;TZID=Europe/Stockholm:20130204T170800
108
+ UID:3cb4df4b-eb18-43fd-934a-16c15acfb4b1-3@jira.idainfront.se
109
+ X-GREENHOPPER-SPRINT-CLOSED:false
110
+ X-GREENHOPPER-SPRINT-EDITABLE:true
111
+ X-JIRA-PROJECT;X-JIRA-PROJECT-KEY=II:iipax
112
+ X-GREENHOPPER-SPRINT-VIEWBOARDS-URL:https://jira.idainfront.se/secure/GHG
113
+ oToBoard.jspa?sprintId=3
114
+ SEQUENCE:0
115
+ X-CONFLUENCE-SUBCALENDAR-TYPE:jira-agile-sprint
116
+ TRANSP:OPAQUE
117
+ STATUS:CONFIRMED
118
+ DESCRIPTION:https://jira.idainfront.se/secure/GHGoToBoard.jspa?sprintId=3
119
+
120
+ SUMMARY:iipax - Callisto-6
121
+ END:VEVENT
122
+ BEGIN:VEVENT
123
+ DTSTAMP:20151203T104348Z
124
+ DTSTART;TZID=Europe/Stockholm:20130219T080000
125
+ DTEND;TZID=Europe/Stockholm:20130319T095900
126
+ UID:3cb4df4b-eb18-43fd-934a-16c15acfb4b1-5@jira.idainfront.se
127
+ X-GREENHOPPER-SPRINT-CLOSED:false
128
+ X-GREENHOPPER-SPRINT-EDITABLE:true
129
+ X-JIRA-PROJECT;X-JIRA-PROJECT-KEY=II:iipax
130
+ X-GREENHOPPER-SPRINT-VIEWBOARDS-URL:https://jira.idainfront.se/secure/GHG
131
+ oToBoard.jspa?sprintId=5
132
+ SEQUENCE:0
133
+ X-CONFLUENCE-SUBCALENDAR-TYPE:jira-agile-sprint
134
+ TRANSP:OPAQUE
135
+ STATUS:CONFIRMED
136
+ DESCRIPTION:https://jira.idainfront.se/secure/GHGoToBoard.jspa?sprintId=5
137
+
138
+ SUMMARY:iipax - Bulbasaur
139
+ END:VEVENT
140
+ BEGIN:VEVENT
141
+ DTSTAMP:20151203T104348Z
142
+ DTSTART;TZID=Europe/Stockholm:20130326T105900
143
+ DTEND;TZID=Europe/Stockholm:20130419T105900
144
+ UID:3cb4df4b-eb18-43fd-934a-16c15acfb4b1-7@jira.idainfront.se
145
+ X-GREENHOPPER-SPRINT-CLOSED:true
146
+ X-GREENHOPPER-SPRINT-EDITABLE:true
147
+ X-JIRA-PROJECT;X-JIRA-PROJECT-KEY=II:iipax
148
+ X-GREENHOPPER-SPRINT-VIEWBOARDS-URL:https://jira.idainfront.se/secure/GHG
149
+ oToBoard.jspa?sprintId=7
150
+ SEQUENCE:0
151
+ X-CONFLUENCE-SUBCALENDAR-TYPE:jira-agile-sprint
152
+ TRANSP:OPAQUE
153
+ STATUS:CONFIRMED
154
+ DESCRIPTION:https://jira.idainfront.se/secure/GHGoToBoard.jspa?sprintId=7
155
+
156
+ SUMMARY:iipax - Ivysaur
157
+ END:VEVENT
158
+ END:VCALENDAR
data/spec/parser_spec.rb CHANGED
@@ -66,6 +66,16 @@ describe Icalendar::Parser do
66
66
  expect(event.dtstart.utc).to eq Time.parse("20180104T150000Z")
67
67
  end
68
68
  end
69
+ context 'custom_component.ics' do
70
+ let(:fn) { 'custom_component.ics' }
71
+
72
+ it 'correctly handles custom named components' do
73
+ parsed = subject.parse
74
+ calendar = parsed.first
75
+ expect(calendar.custom_component('x_event_series').size).to eq 1
76
+ expect(calendar.custom_component('X-EVENT-SERIES').size).to eq 1
77
+ end
78
+ end
69
79
  end
70
80
 
71
81
  describe '#parse with bad line' do
@@ -135,6 +135,15 @@ describe Icalendar do
135
135
  ical = subject.parse.first.to_ical
136
136
  expect(ical).to include 'CUSTOMFIELD:Not properly noted as custom with X- prefix.'
137
137
  end
138
+
139
+ context 'custom components' do
140
+ let(:source) { File.read File.join(File.dirname(__FILE__), 'fixtures', 'custom_component.ics') }
141
+
142
+ it 'can output the custom component' do
143
+ ical = subject.parse.first.to_ical
144
+ expect(ical).to include 'BEGIN:X-EVENT-SERIES'
145
+ end
146
+ end
138
147
  end
139
148
  end
140
149
  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.6.1
4
+ version: 2.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ryan Ahearn
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-12-07 00:00:00.000000000 Z
11
+ date: 2022-07-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ice_cube
@@ -30,14 +30,14 @@ dependencies:
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '12.0'
33
+ version: '13.0'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '12.0'
40
+ version: '13.0'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: bundler
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -58,28 +58,28 @@ dependencies:
58
58
  requirements:
59
59
  - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: '5.2'
61
+ version: '6.0'
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
- version: '5.2'
68
+ version: '6.0'
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: i18n
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
73
  - - "~>"
74
74
  - !ruby/object:Gem::Version
75
- version: '1.1'
75
+ version: '1.8'
76
76
  type: :development
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
- version: '1.1'
82
+ version: '1.8'
83
83
  - !ruby/object:Gem::Dependency
84
84
  name: tzinfo
85
85
  requirement: !ruby/object:Gem::Requirement
@@ -100,14 +100,14 @@ dependencies:
100
100
  requirements:
101
101
  - - "~>"
102
102
  - !ruby/object:Gem::Version
103
- version: '1.2018'
103
+ version: '1.2020'
104
104
  type: :development
105
105
  prerelease: false
106
106
  version_requirements: !ruby/object:Gem::Requirement
107
107
  requirements:
108
108
  - - "~>"
109
109
  - !ruby/object:Gem::Version
110
- version: '1.2018'
110
+ version: '1.2020'
111
111
  - !ruby/object:Gem::Dependency
112
112
  name: timecop
113
113
  requirement: !ruby/object:Gem::Requirement
@@ -160,9 +160,9 @@ executables: []
160
160
  extensions: []
161
161
  extra_rdoc_files: []
162
162
  files:
163
+ - ".github/workflows/main.yml"
163
164
  - ".gitignore"
164
165
  - ".rspec"
165
- - ".travis.yml"
166
166
  - Gemfile
167
167
  - History.txt
168
168
  - LICENSE
@@ -210,6 +210,8 @@ files:
210
210
  - spec/calendar_spec.rb
211
211
  - spec/downcased_hash_spec.rb
212
212
  - spec/event_spec.rb
213
+ - spec/fixtures/bad_wrapping.ics
214
+ - spec/fixtures/custom_component.ics
213
215
  - spec/fixtures/event.ics
214
216
  - spec/fixtures/nondefault_values.ics
215
217
  - spec/fixtures/nonstandard.ics
@@ -241,7 +243,10 @@ files:
241
243
  - spec/values/text_spec.rb
242
244
  - spec/values/utc_offset_spec.rb
243
245
  homepage: https://github.com/icalendar/icalendar
244
- licenses: []
246
+ licenses:
247
+ - BSD-2-Clause
248
+ - GPL-3.0-only
249
+ - icalendar
245
250
  metadata: {}
246
251
  post_install_message: 'ActiveSupport is required for TimeWithZone support, but not
247
252
  required for general use.
@@ -261,8 +266,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
261
266
  - !ruby/object:Gem::Version
262
267
  version: '0'
263
268
  requirements: []
264
- rubygems_version: 3.0.3
265
- signing_key:
269
+ rubygems_version: 3.3.7
270
+ signing_key:
266
271
  specification_version: 4
267
272
  summary: A ruby implementation of the iCalendar specification (RFC-5545).
268
273
  test_files:
@@ -270,6 +275,8 @@ test_files:
270
275
  - spec/calendar_spec.rb
271
276
  - spec/downcased_hash_spec.rb
272
277
  - spec/event_spec.rb
278
+ - spec/fixtures/bad_wrapping.ics
279
+ - spec/fixtures/custom_component.ics
273
280
  - spec/fixtures/event.ics
274
281
  - spec/fixtures/nondefault_values.ics
275
282
  - spec/fixtures/nonstandard.ics
data/.travis.yml DELETED
@@ -1,16 +0,0 @@
1
- sudo: false
2
- before_install:
3
- - gem install bundler
4
- language: ruby
5
- rvm:
6
- - 2.6
7
- - 2.5
8
- - 2.4
9
- - jruby-19mode
10
- - ruby-head
11
- - jruby-head
12
- matrix:
13
- allow_failures:
14
- - rvm: ruby-head
15
- - rvm: jruby-head
16
- script: bundle exec rake spec