icalendar 2.7.1 → 2.9.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 +4 -4
- data/.github/workflows/main.yml +33 -0
- data/History.txt +14 -0
- data/icalendar.gemspec +1 -0
- data/lib/icalendar/alarm.rb +3 -0
- data/lib/icalendar/calendar.rb +48 -0
- data/lib/icalendar/component.rb +8 -2
- data/lib/icalendar/downcased_hash.rb +2 -0
- data/lib/icalendar/event.rb +6 -1
- data/lib/icalendar/freebusy.rb +2 -0
- data/lib/icalendar/has_components.rb +7 -2
- data/lib/icalendar/has_properties.rb +20 -14
- data/lib/icalendar/journal.rb +4 -0
- data/lib/icalendar/logger.rb +2 -0
- data/lib/icalendar/marshable.rb +2 -0
- data/lib/icalendar/parser.rb +39 -15
- data/lib/icalendar/timezone.rb +2 -0
- data/lib/icalendar/timezone_store.rb +2 -0
- data/lib/icalendar/todo.rb +6 -1
- data/lib/icalendar/tzinfo.rb +2 -0
- data/lib/icalendar/value.rb +10 -2
- data/lib/icalendar/values/active_support_time_with_zone_adapter.rb +2 -0
- data/lib/icalendar/values/array.rb +2 -1
- data/lib/icalendar/values/binary.rb +5 -1
- data/lib/icalendar/values/boolean.rb +2 -0
- data/lib/icalendar/values/cal_address.rb +2 -0
- data/lib/icalendar/values/date.rb +2 -0
- data/lib/icalendar/values/date_time.rb +2 -0
- data/lib/icalendar/values/duration.rb +15 -6
- data/lib/icalendar/values/float.rb +2 -0
- data/lib/icalendar/values/integer.rb +2 -0
- data/lib/icalendar/values/period.rb +5 -1
- data/lib/icalendar/values/recur.rb +31 -14
- data/lib/icalendar/values/text.rb +5 -1
- data/lib/icalendar/values/time.rb +2 -0
- data/lib/icalendar/values/time_with_zone.rb +8 -0
- data/lib/icalendar/values/uri.rb +2 -0
- data/lib/icalendar/values/utc_offset.rb +6 -1
- data/lib/icalendar/version.rb +3 -1
- data/lib/icalendar.rb +2 -0
- data/spec/alarm_spec.rb +7 -2
- data/spec/calendar_spec.rb +68 -0
- data/spec/event_spec.rb +4 -2
- metadata +8 -5
- data/.travis.yml +0 -17
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0f083428b61c781e197d459b5ba375139056f75e95b1b99e596169106a57ed20
|
4
|
+
data.tar.gz: acb3cdb07b0dc052664fc67619fa6301951daf70ac83a0430a839a0bdd3a5923
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 595032415b8465be6f662f136a56dd6f4dd8af27e888c7a7b27ad1c688b3963466e34a949daa0111a65ea89037b33ea01d8f2ea24991f5d9a3869647521b6c7c
|
7
|
+
data.tar.gz: ac464077d0c421d27b39270f1d12af23b59383e3e8351b68ec41a8385cc26670ca31b2f8dba72212e8db0996ea4a9e7c4a1b7b6a9b1b34d632169fae5a715725
|
@@ -0,0 +1,33 @@
|
|
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
|
+
- 3.2.2
|
24
|
+
|
25
|
+
steps:
|
26
|
+
- uses: actions/checkout@v2
|
27
|
+
- name: Set up Ruby
|
28
|
+
uses: ruby/setup-ruby@v1
|
29
|
+
with:
|
30
|
+
ruby-version: ${{ matrix.ruby }}
|
31
|
+
bundler-cache: true
|
32
|
+
- name: Run rspec tests
|
33
|
+
run: bundle exec rake spec
|
data/History.txt
CHANGED
@@ -1,3 +1,17 @@
|
|
1
|
+
=== Unreleased
|
2
|
+
|
3
|
+
=== 2.9.0 2023-08-11
|
4
|
+
* Always include the VALUE of Event URLs for improved compatibility - Sean Kelley
|
5
|
+
* Improved parse performance - Thomas Cannon
|
6
|
+
* Add helper methods for other Calendar method verbs
|
7
|
+
* bugfix: Require stringio before use in Parser - vwyu
|
8
|
+
|
9
|
+
=== 2.8.0 2022-07-10
|
10
|
+
* Fix compatibility with ActiveSupport 7 - Pat Allan
|
11
|
+
* Set default action of "DISPLAY" on alarms - Rikson
|
12
|
+
* Add license information to gemspec - Robert Reiz
|
13
|
+
* Support RFC7986 properties - Daniele Frisanco
|
14
|
+
|
1
15
|
=== 2.7.1 2021-03-14
|
2
16
|
* Recover from bad line-wrapping code that splits in the middle of Unicode code points
|
3
17
|
* Add a verbose option to the Parser to quiet some of the chattier log entries
|
data/icalendar.gemspec
CHANGED
data/lib/icalendar/alarm.rb
CHANGED
data/lib/icalendar/calendar.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Icalendar
|
2
4
|
|
3
5
|
class Calendar < Component
|
@@ -5,6 +7,16 @@ module Icalendar
|
|
5
7
|
required_property :prodid
|
6
8
|
optional_single_property :calscale
|
7
9
|
optional_single_property :ip_method
|
10
|
+
optional_property :ip_name
|
11
|
+
optional_property :description
|
12
|
+
optional_single_property :uid
|
13
|
+
optional_single_property :last_modified, Icalendar::Values::DateTime, true
|
14
|
+
optional_single_property :url, Icalendar::Values::Uri, true
|
15
|
+
optional_property :categories
|
16
|
+
optional_single_property :refresh_interval, Icalendar::Values::Duration, true
|
17
|
+
optional_single_property :source, Icalendar::Values::Uri, true
|
18
|
+
optional_single_property :color
|
19
|
+
optional_property :image, Icalendar::Values::Uri, false, true
|
8
20
|
|
9
21
|
component :timezone, :tzid
|
10
22
|
component :event
|
@@ -21,6 +33,42 @@ module Icalendar
|
|
21
33
|
|
22
34
|
def publish
|
23
35
|
self.ip_method = 'PUBLISH'
|
36
|
+
self
|
37
|
+
end
|
38
|
+
|
39
|
+
def request
|
40
|
+
self.ip_method = 'REQUEST'
|
41
|
+
self
|
42
|
+
end
|
43
|
+
|
44
|
+
def reply
|
45
|
+
self.ip_method = 'REPLY'
|
46
|
+
self
|
47
|
+
end
|
48
|
+
|
49
|
+
def add
|
50
|
+
self.ip_method = 'ADD'
|
51
|
+
self
|
52
|
+
end
|
53
|
+
|
54
|
+
def cancel
|
55
|
+
self.ip_method = 'CANCEL'
|
56
|
+
self
|
57
|
+
end
|
58
|
+
|
59
|
+
def refresh
|
60
|
+
self.ip_method = 'REFRESH'
|
61
|
+
self
|
62
|
+
end
|
63
|
+
|
64
|
+
def counter
|
65
|
+
self.ip_method = 'COUNTER'
|
66
|
+
self
|
67
|
+
end
|
68
|
+
|
69
|
+
def decline_counter
|
70
|
+
self.ip_method = 'DECLINECOUNTER'
|
71
|
+
self
|
24
72
|
end
|
25
73
|
|
26
74
|
end
|
data/lib/icalendar/component.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'securerandom'
|
2
4
|
|
3
5
|
module Icalendar
|
@@ -53,10 +55,14 @@ module Icalendar
|
|
53
55
|
end.compact.join "\r\n"
|
54
56
|
end
|
55
57
|
|
58
|
+
ICAL_PROP_NAME_GSUB_REGEX = /\Aip_/.freeze
|
59
|
+
|
56
60
|
def ical_prop_name(prop_name)
|
57
|
-
prop_name.gsub(
|
61
|
+
prop_name.gsub(ICAL_PROP_NAME_GSUB_REGEX, '').gsub('_', '-').upcase
|
58
62
|
end
|
59
63
|
|
64
|
+
ICAL_FOLD_LONG_LINE_SCAN_REGEX = /\P{M}\p{M}*/u.freeze
|
65
|
+
|
60
66
|
def ical_fold(long_line, indent = "\x20")
|
61
67
|
# rfc2445 says:
|
62
68
|
# Lines of text SHOULD NOT be longer than 75 octets, excluding the line
|
@@ -74,7 +80,7 @@ module Icalendar
|
|
74
80
|
|
75
81
|
return long_line if long_line.bytesize <= Icalendar::MAX_LINE_LENGTH
|
76
82
|
|
77
|
-
chars = long_line.scan(
|
83
|
+
chars = long_line.scan(ICAL_FOLD_LONG_LINE_SCAN_REGEX) # split in graphenes
|
78
84
|
folded = ['']
|
79
85
|
bytes = 0
|
80
86
|
while chars.count > 0
|
data/lib/icalendar/event.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Icalendar
|
2
4
|
|
3
5
|
class Event < Component
|
@@ -12,6 +14,7 @@ module Icalendar
|
|
12
14
|
mutually_exclusive_properties :dtend, :duration
|
13
15
|
|
14
16
|
optional_single_property :ip_class
|
17
|
+
optional_single_property :color
|
15
18
|
optional_single_property :created, Icalendar::Values::DateTime
|
16
19
|
optional_single_property :description
|
17
20
|
optional_single_property :geo, Icalendar::Values::Float
|
@@ -23,7 +26,7 @@ module Icalendar
|
|
23
26
|
optional_single_property :status
|
24
27
|
optional_single_property :summary
|
25
28
|
optional_single_property :transp
|
26
|
-
optional_single_property :url, Icalendar::Values::Uri
|
29
|
+
optional_single_property :url, Icalendar::Values::Uri, true
|
27
30
|
optional_single_property :recurrence_id, Icalendar::Values::DateTime
|
28
31
|
|
29
32
|
optional_property :rrule, Icalendar::Values::Recur, true
|
@@ -37,6 +40,8 @@ module Icalendar
|
|
37
40
|
optional_property :related_to
|
38
41
|
optional_property :resources
|
39
42
|
optional_property :rdate, Icalendar::Values::DateTime
|
43
|
+
optional_property :conference, Icalendar::Values::Uri, false, true
|
44
|
+
optional_property :image, Icalendar::Values::Uri, false, true
|
40
45
|
|
41
46
|
component :alarm, false
|
42
47
|
|
data/lib/icalendar/freebusy.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Icalendar
|
2
4
|
|
3
5
|
module HasComponents
|
@@ -32,13 +34,16 @@ module Icalendar
|
|
32
34
|
custom_components[component_name.downcase.gsub("-", "_")] || []
|
33
35
|
end
|
34
36
|
|
37
|
+
METHOD_MISSING_ADD_REGEX = /^add_(x_\w+)$/.freeze
|
38
|
+
METHOD_MISSING_X_FLAG_REGEX = /^x_/.freeze
|
39
|
+
|
35
40
|
def method_missing(method, *args, &block)
|
36
41
|
method_name = method.to_s
|
37
|
-
if method_name =~
|
42
|
+
if method_name =~ METHOD_MISSING_ADD_REGEX
|
38
43
|
component_name = $1
|
39
44
|
custom = args.first || Component.new(component_name, component_name.upcase)
|
40
45
|
add_custom_component(component_name, custom, &block)
|
41
|
-
elsif method_name =~
|
46
|
+
elsif method_name =~ METHOD_MISSING_X_FLAG_REGEX && custom_component(method_name).size > 0
|
42
47
|
custom_component method_name
|
43
48
|
else
|
44
49
|
super
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Icalendar
|
2
4
|
|
3
5
|
module HasProperties
|
@@ -104,29 +106,29 @@ module Icalendar
|
|
104
106
|
def required_property(prop, klass = Icalendar::Values::Text, validator = nil)
|
105
107
|
validator ||= ->(component, value) { !value.nil? }
|
106
108
|
self.required_properties[prop] = validator
|
107
|
-
single_property prop, klass
|
109
|
+
single_property prop, klass, false
|
108
110
|
end
|
109
111
|
|
110
112
|
def required_multi_property(prop, klass = Icalendar::Values::Text, validator = nil)
|
111
113
|
validator ||= ->(component, value) { !value.compact.empty? }
|
112
114
|
self.required_properties[prop] = validator
|
113
|
-
multi_property prop, klass
|
115
|
+
multi_property prop, klass, false
|
114
116
|
end
|
115
117
|
|
116
|
-
def optional_single_property(prop, klass = Icalendar::Values::Text)
|
117
|
-
single_property prop, klass
|
118
|
+
def optional_single_property(prop, klass = Icalendar::Values::Text, new_property = false)
|
119
|
+
single_property prop, klass, new_property
|
118
120
|
end
|
119
121
|
|
120
122
|
def mutually_exclusive_properties(*properties)
|
121
123
|
self.mutex_properties << properties
|
122
124
|
end
|
123
125
|
|
124
|
-
def optional_property(prop, klass = Icalendar::Values::Text, suggested_single = false)
|
126
|
+
def optional_property(prop, klass = Icalendar::Values::Text, suggested_single = false, new_property = false)
|
125
127
|
self.suggested_single_properties << prop if suggested_single
|
126
|
-
multi_property prop, klass
|
128
|
+
multi_property prop, klass, new_property
|
127
129
|
end
|
128
130
|
|
129
|
-
def single_property(prop, klass)
|
131
|
+
def single_property(prop, klass, new_property)
|
130
132
|
self.single_properties << prop.to_s
|
131
133
|
self.default_property_types[prop.to_s] = klass
|
132
134
|
|
@@ -135,17 +137,17 @@ module Icalendar
|
|
135
137
|
end
|
136
138
|
|
137
139
|
define_method "#{prop}=" do |value|
|
138
|
-
instance_variable_set "@#{prop}", map_property_value(value, klass, false)
|
140
|
+
instance_variable_set "@#{prop}", map_property_value(value, klass, false, new_property)
|
139
141
|
end
|
140
142
|
end
|
141
143
|
|
142
|
-
def multi_property(prop, klass)
|
144
|
+
def multi_property(prop, klass, new_property)
|
143
145
|
self.multiple_properties << prop.to_s
|
144
146
|
self.default_property_types[prop.to_s] = klass
|
145
147
|
property_var = "@#{prop}"
|
146
148
|
|
147
149
|
define_method "#{prop}=" do |value|
|
148
|
-
mapped = map_property_value value, klass, true
|
150
|
+
mapped = map_property_value value, klass, true, new_property
|
149
151
|
if mapped.is_a? Icalendar::Values::Array
|
150
152
|
instance_variable_set property_var, mapped.to_a.compact
|
151
153
|
else
|
@@ -162,20 +164,24 @@ module Icalendar
|
|
162
164
|
end
|
163
165
|
|
164
166
|
define_method "append_#{prop}" do |value|
|
165
|
-
send(prop) << map_property_value(value, klass, true)
|
167
|
+
send(prop) << map_property_value(value, klass, true, new_property)
|
166
168
|
end
|
167
169
|
end
|
168
170
|
end
|
169
171
|
|
170
172
|
private
|
171
173
|
|
172
|
-
def map_property_value(value, klass, multi_valued)
|
174
|
+
def map_property_value(value, klass, multi_valued, new_property)
|
175
|
+
params = {}
|
176
|
+
if new_property
|
177
|
+
params.merge!('VALUE': klass.value_type)
|
178
|
+
end
|
173
179
|
if value.nil? || value.is_a?(Icalendar::Value)
|
174
180
|
value
|
175
181
|
elsif value.is_a? ::Array
|
176
|
-
Icalendar::Values::Array.new value, klass,
|
182
|
+
Icalendar::Values::Array.new value, klass, params, {delimiter: (multi_valued ? ',' : ';')}
|
177
183
|
else
|
178
|
-
klass.new value
|
184
|
+
klass.new value, params
|
179
185
|
end
|
180
186
|
end
|
181
187
|
|
data/lib/icalendar/journal.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Icalendar
|
2
4
|
|
3
5
|
class Journal < Component
|
@@ -6,6 +8,7 @@ module Icalendar
|
|
6
8
|
required_property :uid
|
7
9
|
|
8
10
|
optional_single_property :ip_class
|
11
|
+
optional_single_property :color
|
9
12
|
optional_single_property :created, Icalendar::Values::DateTime
|
10
13
|
optional_single_property :dtstart, Icalendar::Values::DateTime
|
11
14
|
optional_single_property :last_modified, Icalendar::Values::DateTime
|
@@ -27,6 +30,7 @@ module Icalendar
|
|
27
30
|
optional_property :request_status
|
28
31
|
optional_property :related_to
|
29
32
|
optional_property :rdate, Icalendar::Values::DateTime
|
33
|
+
optional_property :image, Icalendar::Values::Uri, false, true
|
30
34
|
|
31
35
|
def initialize
|
32
36
|
super 'journal'
|
data/lib/icalendar/logger.rb
CHANGED
data/lib/icalendar/marshable.rb
CHANGED
data/lib/icalendar/parser.rb
CHANGED
@@ -1,4 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'icalendar/timezone_store'
|
4
|
+
require 'stringio'
|
2
5
|
|
3
6
|
module Icalendar
|
4
7
|
|
@@ -6,6 +9,8 @@ module Icalendar
|
|
6
9
|
attr_writer :component_class
|
7
10
|
attr_reader :source, :strict, :timezone_store, :verbose
|
8
11
|
|
12
|
+
CLEAN_BAD_WRAPPING_GSUB_REGEX = /\r?\n[ \t]/.freeze
|
13
|
+
|
9
14
|
def self.clean_bad_wrapping(source)
|
10
15
|
content = if source.respond_to? :read
|
11
16
|
source.read
|
@@ -18,7 +23,7 @@ module Icalendar
|
|
18
23
|
end
|
19
24
|
encoding = content.encoding
|
20
25
|
content.force_encoding(Encoding::ASCII_8BIT)
|
21
|
-
content.gsub(
|
26
|
+
content.gsub(CLEAN_BAD_WRAPPING_GSUB_REGEX, "").force_encoding(encoding)
|
22
27
|
end
|
23
28
|
|
24
29
|
def initialize(source, strict = false, verbose = false)
|
@@ -50,7 +55,7 @@ module Icalendar
|
|
50
55
|
|
51
56
|
def parse_property(component, fields = nil)
|
52
57
|
fields = next_fields if fields.nil?
|
53
|
-
prop_name = %w(class method).include?(fields[:name]) ? "ip_#{fields[:name]}" : fields[:name]
|
58
|
+
prop_name = %w(class method name).include?(fields[:name]) ? "ip_#{fields[:name]}" : fields[:name]
|
54
59
|
multi_property = component.class.multiple_properties.include? prop_name
|
55
60
|
prop_value = wrap_property_value component, fields, multi_property
|
56
61
|
begin
|
@@ -71,11 +76,15 @@ module Icalendar
|
|
71
76
|
end
|
72
77
|
end
|
73
78
|
|
79
|
+
WRAP_PROPERTY_VALUE_DELIMETER_REGEX = /(?<!\\)([,;])/.freeze
|
80
|
+
WRAP_PROPERTY_VALUE_SPLIT_REGEX = /(?<!\\)[;,]/.freeze
|
81
|
+
|
82
|
+
|
74
83
|
def wrap_property_value(component, fields, multi_property)
|
75
84
|
klass = get_wrapper_class component, fields
|
76
85
|
if wrap_in_array? klass, fields[:value], multi_property
|
77
|
-
delimiter = fields[:value].match(
|
78
|
-
Icalendar::Values::Array.new fields[:value].split(
|
86
|
+
delimiter = fields[:value].match(WRAP_PROPERTY_VALUE_DELIMETER_REGEX)[1]
|
87
|
+
Icalendar::Values::Array.new fields[:value].split(WRAP_PROPERTY_VALUE_SPLIT_REGEX),
|
79
88
|
klass,
|
80
89
|
fields[:params],
|
81
90
|
delimiter: delimiter
|
@@ -88,17 +97,22 @@ module Icalendar
|
|
88
97
|
retry
|
89
98
|
end
|
90
99
|
|
100
|
+
WRAP_IN_ARRAY_REGEX_1 = /(?<!\\)[,;]/.freeze
|
101
|
+
WRAP_IN_ARRAY_REGEX_2 = /(?<!\\);/.freeze
|
102
|
+
|
91
103
|
def wrap_in_array?(klass, value, multi_property)
|
92
104
|
klass.value_type != 'RECUR' &&
|
93
|
-
((multi_property && value =~
|
105
|
+
((multi_property && value =~ WRAP_IN_ARRAY_REGEX_1) || value =~ WRAP_IN_ARRAY_REGEX_2)
|
94
106
|
end
|
95
107
|
|
108
|
+
GET_WRAPPER_CLASS_GSUB_REGEX = /(?:\A|-)(.)/.freeze
|
109
|
+
|
96
110
|
def get_wrapper_class(component, fields)
|
97
111
|
klass = component.class.default_property_types[fields[:name]]
|
98
112
|
if !fields[:params]['value'].nil?
|
99
113
|
klass_name = fields[:params].delete('value').first
|
100
114
|
unless klass_name.upcase == klass.value_type
|
101
|
-
klass_name = "Icalendar::Values::#{klass_name.downcase.gsub(
|
115
|
+
klass_name = "Icalendar::Values::#{klass_name.downcase.gsub(GET_WRAPPER_CLASS_GSUB_REGEX) { |m| m[-1].upcase }}"
|
102
116
|
klass = Object.const_get klass_name if Object.const_defined?(klass_name)
|
103
117
|
end
|
104
118
|
end
|
@@ -119,14 +133,16 @@ module Icalendar
|
|
119
133
|
@component_class ||= Icalendar::Calendar
|
120
134
|
end
|
121
135
|
|
136
|
+
PARSE_COMPONENT_KLASS_NAME_GSUB_REGEX = /\AV/.freeze
|
137
|
+
|
122
138
|
def parse_component(component)
|
123
139
|
while (fields = next_fields)
|
124
140
|
if fields[:name] == 'end'
|
125
|
-
klass_name = fields[:value].gsub(
|
141
|
+
klass_name = fields[:value].gsub(PARSE_COMPONENT_KLASS_NAME_GSUB_REGEX, '').downcase.capitalize
|
126
142
|
timezone_store.store(component) if klass_name == 'Timezone'
|
127
143
|
break
|
128
144
|
elsif fields[:name] == 'begin'
|
129
|
-
klass_name = fields[:value].gsub(
|
145
|
+
klass_name = fields[:value].gsub(PARSE_COMPONENT_KLASS_NAME_GSUB_REGEX, '').gsub("-", "_").downcase.capitalize
|
130
146
|
Icalendar.logger.debug "Adding component #{klass_name}"
|
131
147
|
if Object.const_defined? "Icalendar::#{klass_name}"
|
132
148
|
component.add_component parse_component(Object.const_get("Icalendar::#{klass_name}").new)
|
@@ -146,13 +162,16 @@ module Icalendar
|
|
146
162
|
@data = source.gets and @data.chomp!
|
147
163
|
end
|
148
164
|
|
165
|
+
NEXT_FIELDS_TAB_REGEX = /\A[ \t].+\z/.freeze
|
166
|
+
NEXT_FIELDS_WHITESPACE_REGEX = /\A\s*\z/.freeze
|
167
|
+
|
149
168
|
def next_fields
|
150
169
|
line = @data or return nil
|
151
170
|
loop do
|
152
171
|
read_in_data
|
153
|
-
if @data =~
|
172
|
+
if @data =~ NEXT_FIELDS_TAB_REGEX
|
154
173
|
line << @data[1, @data.size]
|
155
|
-
elsif @data !~
|
174
|
+
elsif @data !~ NEXT_FIELDS_WHITESPACE_REGEX
|
156
175
|
break
|
157
176
|
end
|
158
177
|
end
|
@@ -167,24 +186,29 @@ module Icalendar
|
|
167
186
|
VALUE = '.*'
|
168
187
|
LINE = "(?<name>#{NAME})(?<params>(?:;#{PARAM})*):(?<value>#{VALUE})"
|
169
188
|
BAD_LINE = "(?<name>#{NAME})(?<params>(?:;#{PARAM})*)"
|
189
|
+
LINE_REGEX = %r{#{LINE}}.freeze
|
190
|
+
BAD_LINE_REGEX = %r{#{BAD_LINE}}.freeze
|
191
|
+
PARAM_REGEX = %r{#{PARAM}}.freeze
|
192
|
+
PVALUE_REGEX = %r{#{PVALUE}}.freeze
|
193
|
+
PVALUE_GSUB_REGEX = /\A"|"\z/.freeze
|
170
194
|
|
171
195
|
def parse_fields(input)
|
172
|
-
if parts =
|
196
|
+
if parts = LINE_REGEX.match(input)
|
173
197
|
value = parts[:value]
|
174
198
|
else
|
175
|
-
parts =
|
199
|
+
parts = BAD_LINE_REGEX.match(input) unless strict?
|
176
200
|
parts or fail "Invalid iCalendar input line: #{input}"
|
177
201
|
# Non-strict and bad line so use a value of empty string
|
178
202
|
value = ''
|
179
203
|
end
|
180
204
|
|
181
205
|
params = {}
|
182
|
-
parts[:params].scan
|
206
|
+
parts[:params].scan PARAM_REGEX do |match|
|
183
207
|
param_name = match[0].downcase
|
184
208
|
params[param_name] ||= []
|
185
|
-
match[1].scan
|
209
|
+
match[1].scan PVALUE_REGEX do |param_value|
|
186
210
|
if param_value.size > 0
|
187
|
-
param_value = param_value.gsub(
|
211
|
+
param_value = param_value.gsub(PVALUE_GSUB_REGEX, '')
|
188
212
|
params[param_name] << param_value
|
189
213
|
if param_name == 'tzid'
|
190
214
|
params['x-tz-info'] = timezone_store.retrieve param_value
|
data/lib/icalendar/timezone.rb
CHANGED
data/lib/icalendar/todo.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Icalendar
|
2
4
|
|
3
5
|
class Todo < Component
|
@@ -12,6 +14,7 @@ module Icalendar
|
|
12
14
|
mutually_exclusive_properties :due, :duration
|
13
15
|
|
14
16
|
optional_single_property :ip_class
|
17
|
+
optional_single_property :color
|
15
18
|
optional_single_property :completed, Icalendar::Values::DateTime
|
16
19
|
optional_single_property :created, Icalendar::Values::DateTime
|
17
20
|
optional_single_property :description
|
@@ -38,6 +41,8 @@ module Icalendar
|
|
38
41
|
optional_property :related_to
|
39
42
|
optional_property :resources
|
40
43
|
optional_property :rdate, Icalendar::Values::DateTime
|
44
|
+
optional_property :conference, Icalendar::Values::Uri, false, true
|
45
|
+
optional_property :image, Icalendar::Values::Uri, false, true
|
41
46
|
|
42
47
|
component :alarm, false
|
43
48
|
|
@@ -49,4 +54,4 @@ module Icalendar
|
|
49
54
|
|
50
55
|
end
|
51
56
|
|
52
|
-
end
|
57
|
+
end
|
data/lib/icalendar/tzinfo.rb
CHANGED
data/lib/icalendar/value.rb
CHANGED
@@ -1,3 +1,6 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
|
1
4
|
require 'delegate'
|
2
5
|
require 'icalendar/downcased_hash'
|
3
6
|
|
@@ -31,8 +34,11 @@ module Icalendar
|
|
31
34
|
end
|
32
35
|
end
|
33
36
|
|
37
|
+
VALUE_TYPE_GSUB_REGEX_1 = /\A.*::/.freeze
|
38
|
+
VALUE_TYPE_GSUB_REGEX_2 = /(?<!\A)[A-Z]/.freeze
|
39
|
+
|
34
40
|
def self.value_type
|
35
|
-
name.gsub(
|
41
|
+
name.gsub(VALUE_TYPE_GSUB_REGEX_1, '').gsub(VALUE_TYPE_GSUB_REGEX_2, '-\0').upcase
|
36
42
|
end
|
37
43
|
|
38
44
|
def value_type
|
@@ -54,9 +60,11 @@ module Icalendar
|
|
54
60
|
"#{name.to_s.gsub('_', '-').upcase}=#{param_value}"
|
55
61
|
end
|
56
62
|
|
63
|
+
ESCAPE_PARAM_VALUE_REGEX = /[;:,]/.freeze
|
64
|
+
|
57
65
|
def escape_param_value(value)
|
58
66
|
v = value.to_s.gsub('"', "'")
|
59
|
-
v =~
|
67
|
+
v =~ ESCAPE_PARAM_VALUE_REGEX ? %("#{v}") : v
|
60
68
|
end
|
61
69
|
|
62
70
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'base64'
|
2
4
|
|
3
5
|
module Icalendar
|
@@ -21,9 +23,11 @@ module Icalendar
|
|
21
23
|
|
22
24
|
private
|
23
25
|
|
26
|
+
BASE_64_REGEX = /\A(?:[A-Za-z0-9+\/]{4})*(?:[A-Za-z0-9+\/]{4}|[A-Za-z0-9+\/]{3}=|[A-Za-z0-9+\/]{2}==)\z/.freeze
|
27
|
+
|
24
28
|
def base64?
|
25
29
|
value.is_a?(String) &&
|
26
|
-
value =~
|
30
|
+
value =~ BASE_64_REGEX
|
27
31
|
end
|
28
32
|
end
|
29
33
|
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'ostruct'
|
2
4
|
|
3
5
|
module Icalendar
|
@@ -36,14 +38,21 @@ module Icalendar
|
|
36
38
|
hours > 0 || minutes > 0 || seconds > 0
|
37
39
|
end
|
38
40
|
|
41
|
+
DURATION_PAST_REGEX = /\A([+-])P/.freeze
|
42
|
+
DURATION_WEEKS_REGEX = /(\d+)W/.freeze
|
43
|
+
DURATION_DAYS_REGEX = /(\d+)D/.freeze
|
44
|
+
DURATION_HOURS_REGEX = /(\d+)H/.freeze
|
45
|
+
DURATION_MINUTES_REGEX = /(\d+)M/.freeze
|
46
|
+
DURATION_SECONDS_REGEX = /(\d+)S/.freeze
|
47
|
+
|
39
48
|
def parse_fields(value)
|
40
49
|
{
|
41
|
-
past: (value =~
|
42
|
-
weeks: (value =~
|
43
|
-
days: (value =~
|
44
|
-
hours: (value =~
|
45
|
-
minutes: (value =~
|
46
|
-
seconds: (value =~
|
50
|
+
past: (value =~ DURATION_PAST_REGEX ? $1 == '-' : false),
|
51
|
+
weeks: (value =~ DURATION_WEEKS_REGEX ? $1.to_i : 0),
|
52
|
+
days: (value =~ DURATION_DAYS_REGEX ? $1.to_i : 0),
|
53
|
+
hours: (value =~ DURATION_HOURS_REGEX ? $1.to_i : 0),
|
54
|
+
minutes: (value =~ DURATION_MINUTES_REGEX ? $1.to_i : 0),
|
55
|
+
seconds: (value =~ DURATION_SECONDS_REGEX ? $1.to_i : 0)
|
47
56
|
}
|
48
57
|
end
|
49
58
|
end
|
@@ -1,12 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Icalendar
|
2
4
|
module Values
|
3
5
|
|
4
6
|
class Period < Value
|
5
7
|
|
8
|
+
PERIOD_LAST_PART_REGEX = /\A[+-]?P.+\z/.freeze
|
9
|
+
|
6
10
|
def initialize(value, params = {})
|
7
11
|
parts = value.split '/'
|
8
12
|
period_start = Icalendar::Values::DateTime.new parts.first
|
9
|
-
if parts.last =~
|
13
|
+
if parts.last =~ PERIOD_LAST_PART_REGEX
|
10
14
|
period_end = Icalendar::Values::Duration.new parts.last
|
11
15
|
else
|
12
16
|
period_end = Icalendar::Values::DateTime.new parts.last
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'ostruct'
|
2
4
|
|
3
5
|
module Icalendar
|
@@ -44,22 +46,37 @@ module Icalendar
|
|
44
46
|
|
45
47
|
private
|
46
48
|
|
49
|
+
PARSE_FIELDS_FREQUENCY_REGEX = /FREQ=(SECONDLY|MINUTELY|HOURLY|DAILY|WEEKLY|MONTHLY|YEARLY)/i.freeze
|
50
|
+
PARSE_FIELDS_UNTIL_REGEX = /UNTIL=([^;]*)/i.freeze
|
51
|
+
PARSE_FIELDS_COUNT_REGEX = /COUNT=(\d+)/i.freeze
|
52
|
+
PARSE_FIELDS_INTERVAL_REGEX = /INTERVAL=(\d+)/i.freeze
|
53
|
+
PARSE_FIELDS_BY_SECOND_REGEX = /BYSECOND=(#{NUM_LIST})(?:;|\z)/i.freeze
|
54
|
+
PARSE_FIELDS_BY_MINUTE_REGEX = /BYMINUTE=(#{NUM_LIST})(?:;|\z)/i.freeze
|
55
|
+
PARSE_FIELDS_BY_HOUR_REGEX = /BYHOUR=(#{NUM_LIST})(?:;|\z)/i.freeze
|
56
|
+
PARSE_FIELDS_BY_DAY_REGEX = /BYDAY=(#{WEEKDAY}(?:,#{WEEKDAY})*)(?:;|\z)/i.freeze
|
57
|
+
PARSE_FIELDS_BY_MONTH_DAY_REGEX = /BYMONTHDAY=(#{MONTHDAY}(?:,#{MONTHDAY})*)(?:;|\z)/i.freeze
|
58
|
+
PARSE_FIELDS_BY_YEAR_DAY_REGEX = /BYYEARDAY=(#{YEARDAY}(?:,#{YEARDAY})*)(?:;|\z)/i.freeze
|
59
|
+
PARSE_FIELDS_BY_WEEK_NUMBER_REGEX = /BYWEEKNO=(#{MONTHDAY}(?:,#{MONTHDAY})*)(?:;|\z)/i.freeze
|
60
|
+
PARSE_FIELDS_BY_MONTH_REGEX = /BYMONTH=(#{NUM_LIST})(?:;|\z)/i.freeze
|
61
|
+
PARSE_FIELDS_BY_SET_POSITON_REGEX = /BYSETPOS=(#{YEARDAY}(?:,#{YEARDAY})*)(?:;|\z)/i.freeze
|
62
|
+
PARSE_FIELDS_BY_WEEK_START_REGEX = /WKST=(#{DAYNAME})/i.freeze
|
63
|
+
|
47
64
|
def parse_fields(value)
|
48
65
|
{
|
49
|
-
frequency: (value =~
|
50
|
-
until: (value =~
|
51
|
-
count: (value =~
|
52
|
-
interval: (value =~
|
53
|
-
by_second: (value =~
|
54
|
-
by_minute: (value =~
|
55
|
-
by_hour: (value =~
|
56
|
-
by_day: (value =~
|
57
|
-
by_month_day: (value =~
|
58
|
-
by_year_day: (value =~
|
59
|
-
by_week_number: (value =~
|
60
|
-
by_month: (value =~
|
61
|
-
by_set_position: (value =~
|
62
|
-
week_start: (value =~
|
66
|
+
frequency: (value =~ PARSE_FIELDS_FREQUENCY_REGEX ? $1.upcase : nil),
|
67
|
+
until: (value =~ PARSE_FIELDS_UNTIL_REGEX ? $1 : nil),
|
68
|
+
count: (value =~ PARSE_FIELDS_COUNT_REGEX ? $1.to_i : nil),
|
69
|
+
interval: (value =~ PARSE_FIELDS_INTERVAL_REGEX ? $1.to_i : nil),
|
70
|
+
by_second: (value =~ PARSE_FIELDS_BY_SECOND_REGEX ? $1.split(',').map { |i| i.to_i } : nil),
|
71
|
+
by_minute: (value =~ PARSE_FIELDS_BY_MINUTE_REGEX ? $1.split(',').map { |i| i.to_i } : nil),
|
72
|
+
by_hour: (value =~ PARSE_FIELDS_BY_HOUR_REGEX ? $1.split(',').map { |i| i.to_i } : nil),
|
73
|
+
by_day: (value =~ PARSE_FIELDS_BY_DAY_REGEX ? $1.split(',') : nil),
|
74
|
+
by_month_day: (value =~ PARSE_FIELDS_BY_MONTH_DAY_REGEX ? $1.split(',') : nil),
|
75
|
+
by_year_day: (value =~ PARSE_FIELDS_BY_YEAR_DAY_REGEX ? $1.split(',') : nil),
|
76
|
+
by_week_number: (value =~ PARSE_FIELDS_BY_WEEK_NUMBER_REGEX ? $1.split(',') : nil),
|
77
|
+
by_month: (value =~ PARSE_FIELDS_BY_MONTH_REGEX ? $1.split(',').map { |i| i.to_i } : nil),
|
78
|
+
by_set_position: (value =~ PARSE_FIELDS_BY_SET_POSITON_REGEX ? $1.split(',') : nil),
|
79
|
+
week_start: (value =~ PARSE_FIELDS_BY_WEEK_START_REGEX ? $1.upcase : nil)
|
63
80
|
}
|
64
81
|
end
|
65
82
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Icalendar
|
2
4
|
module Values
|
3
5
|
class Text < Value
|
@@ -9,12 +11,14 @@ module Icalendar
|
|
9
11
|
super value, params
|
10
12
|
end
|
11
13
|
|
14
|
+
VALUE_ICAL_CARRIAGE_RETURN_GSUB_REGEX = /\r?\n/.freeze
|
15
|
+
|
12
16
|
def value_ical
|
13
17
|
value.dup.tap do |v|
|
14
18
|
v.gsub!('\\') { '\\\\' }
|
15
19
|
v.gsub!(';', '\;')
|
16
20
|
v.gsub!(',', '\,')
|
17
|
-
v.gsub!(
|
21
|
+
v.gsub!(VALUE_ICAL_CARRIAGE_RETURN_GSUB_REGEX, '\n')
|
18
22
|
end
|
19
23
|
end
|
20
24
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'icalendar/timezone_store'
|
2
4
|
|
3
5
|
begin
|
@@ -6,6 +8,12 @@ begin
|
|
6
8
|
if defined?(ActiveSupport::TimeWithZone)
|
7
9
|
require 'icalendar/values/active_support_time_with_zone_adapter'
|
8
10
|
end
|
11
|
+
rescue NameError
|
12
|
+
# ActiveSupport v7+ needs the base require to be run first before loading
|
13
|
+
# specific parts of it.
|
14
|
+
# https://guides.rubyonrails.org/active_support_core_extensions.html#stand-alone-active-support
|
15
|
+
require 'active_support'
|
16
|
+
retry
|
9
17
|
rescue LoadError
|
10
18
|
# tis ok, just a bit less fancy
|
11
19
|
end
|
data/lib/icalendar/values/uri.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'ostruct'
|
2
4
|
|
3
5
|
module Icalendar
|
@@ -36,8 +38,11 @@ module Icalendar
|
|
36
38
|
hours == 0 && minutes == 0 && seconds == 0
|
37
39
|
end
|
38
40
|
|
41
|
+
PARSE_FIELDS_MD_REGEX = /\A(?<behind>[+-])(?<hours>\d{2})(?<minutes>\d{2})(?<seconds>\d{2})?\z/.freeze
|
42
|
+
PARSE_FIELDS_WHITESPACE_GSUB_REGEX = /\s+/.freeze
|
43
|
+
|
39
44
|
def parse_fields(value)
|
40
|
-
md =
|
45
|
+
md = PARSE_FIELDS_MD_REGEX.match value.gsub(PARSE_FIELDS_WHITESPACE_GSUB_REGEX, '')
|
41
46
|
{
|
42
47
|
behind: (md[:behind] == '-'),
|
43
48
|
hours: md[:hours].to_i,
|
data/lib/icalendar/version.rb
CHANGED
data/lib/icalendar.rb
CHANGED
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'
|
data/spec/calendar_spec.rb
CHANGED
@@ -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
|
@@ -171,6 +190,55 @@ END:VCALENDAR
|
|
171
190
|
end
|
172
191
|
end
|
173
192
|
|
193
|
+
describe '#request' do
|
194
|
+
it 'sets ip_method to "REQUEST"' do
|
195
|
+
subject.request
|
196
|
+
expect(subject.ip_method).to eq 'REQUEST'
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
describe '#reply' do
|
201
|
+
it 'sets ip_method to "REPLY"' do
|
202
|
+
subject.reply
|
203
|
+
expect(subject.ip_method).to eq 'REPLY'
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
describe '#add' do
|
208
|
+
it 'sets ip_method to "ADD"' do
|
209
|
+
subject.add
|
210
|
+
expect(subject.ip_method).to eq 'ADD'
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
describe '#cancel' do
|
215
|
+
it 'sets ip_method to "CANCEL"' do
|
216
|
+
subject.cancel
|
217
|
+
expect(subject.ip_method).to eq 'CANCEL'
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
describe '#refresh' do
|
222
|
+
it 'sets ip_method to "REFRESH"' do
|
223
|
+
subject.refresh
|
224
|
+
expect(subject.ip_method).to eq 'REFRESH'
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
describe '#counter' do
|
229
|
+
it 'sets ip_method to "COUNTER"' do
|
230
|
+
subject.counter
|
231
|
+
expect(subject.ip_method).to eq 'COUNTER'
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
describe '#decline_counter' do
|
236
|
+
it 'sets ip_method to "DECLINECOUNTER"' do
|
237
|
+
subject.decline_counter
|
238
|
+
expect(subject.ip_method).to eq 'DECLINECOUNTER'
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
174
242
|
describe '.parse' do
|
175
243
|
let(:source) { File.read File.join(File.dirname(__FILE__), 'fixtures', 'bad_wrapping.ics') }
|
176
244
|
|
data/spec/event_spec.rb
CHANGED
@@ -51,8 +51,8 @@ describe Icalendar::Event do
|
|
51
51
|
context 'suggested single values' do
|
52
52
|
before(:each) do
|
53
53
|
subject.dtstart = DateTime.now
|
54
|
-
subject.append_rrule
|
55
|
-
subject.append_rrule
|
54
|
+
subject.append_rrule 'RRule'
|
55
|
+
subject.append_rrule 'RRule'
|
56
56
|
end
|
57
57
|
|
58
58
|
it 'is valid by default' do
|
@@ -156,6 +156,7 @@ describe Icalendar::Event do
|
|
156
156
|
subject.dtend = "20131227T033000Z"
|
157
157
|
subject.summary = 'My event, my ical, my test'
|
158
158
|
subject.geo = [41.230896,-74.411774]
|
159
|
+
subject.url = 'https://example.com'
|
159
160
|
subject.x_custom_property = 'customize'
|
160
161
|
end
|
161
162
|
|
@@ -163,6 +164,7 @@ describe Icalendar::Event do
|
|
163
164
|
it { expect(subject.to_ical).to include 'DTEND:20131227T033000Z' }
|
164
165
|
it { expect(subject.to_ical).to include 'SUMMARY:My event\, my ical\, my test' }
|
165
166
|
it { expect(subject.to_ical).to include 'X-CUSTOM-PROPERTY:customize' }
|
167
|
+
it { expect(subject.to_ical).to include 'URL;VALUE=URI:https://example.com' }
|
166
168
|
it { expect(subject.to_ical).to include 'GEO:41.230896;-74.411774' }
|
167
169
|
|
168
170
|
context 'simple organizer' do
|
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.
|
4
|
+
version: 2.9.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:
|
11
|
+
date: 2023-08-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: ice_cube
|
@@ -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
|
@@ -243,7 +243,10 @@ files:
|
|
243
243
|
- spec/values/text_spec.rb
|
244
244
|
- spec/values/utc_offset_spec.rb
|
245
245
|
homepage: https://github.com/icalendar/icalendar
|
246
|
-
licenses:
|
246
|
+
licenses:
|
247
|
+
- BSD-2-Clause
|
248
|
+
- GPL-3.0-only
|
249
|
+
- icalendar
|
247
250
|
metadata: {}
|
248
251
|
post_install_message: 'ActiveSupport is required for TimeWithZone support, but not
|
249
252
|
required for general use.
|
@@ -263,7 +266,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
263
266
|
- !ruby/object:Gem::Version
|
264
267
|
version: '0'
|
265
268
|
requirements: []
|
266
|
-
rubygems_version: 3.
|
269
|
+
rubygems_version: 3.3.7
|
267
270
|
signing_key:
|
268
271
|
specification_version: 4
|
269
272
|
summary: A ruby implementation of the iCalendar specification (RFC-5545).
|
data/.travis.yml
DELETED
@@ -1,17 +0,0 @@
|
|
1
|
-
sudo: false
|
2
|
-
before_install:
|
3
|
-
- gem install bundler
|
4
|
-
language: ruby
|
5
|
-
rvm:
|
6
|
-
- 2.7
|
7
|
-
- 2.6
|
8
|
-
- 2.5
|
9
|
-
- jruby-19mode
|
10
|
-
- ruby-head
|
11
|
-
- jruby-head
|
12
|
-
- truffleruby-head
|
13
|
-
matrix:
|
14
|
-
allow_failures:
|
15
|
-
- rvm: ruby-head
|
16
|
-
- rvm: jruby-head
|
17
|
-
script: bundle exec rake spec
|