icalendar 2.7.1 → 2.10.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/main.yml +32 -0
- data/History.txt +22 -0
- data/icalendar.gemspec +12 -6
- 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 +21 -15
- 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 +40 -16
- data/lib/icalendar/timezone.rb +8 -4
- 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 +11 -3
- 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 +3 -1
- data/lib/icalendar/values/date_time.rb +4 -2
- data/lib/icalendar/values/duration.rb +15 -6
- data/lib/icalendar/values/float.rb +2 -0
- data/lib/icalendar/values/helpers/active_support_time_with_zone_adapter.rb +16 -0
- data/lib/icalendar/values/helpers/array.rb +62 -0
- data/lib/icalendar/values/helpers/time_with_zone.rb +86 -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 +4 -2
- 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
- data/spec/fixtures/reversed_timezone.ics +143 -0
- data/spec/parser_spec.rb +9 -0
- data/spec/roundtrip_spec.rb +1 -1
- data/spec/values/date_time_spec.rb +16 -0
- metadata +33 -27
- data/.travis.yml +0 -17
- data/lib/icalendar/values/active_support_time_with_zone_adapter.rb +0 -12
- data/lib/icalendar/values/array.rb +0 -59
- data/lib/icalendar/values/time_with_zone.rb +0 -47
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7b9f7e30ef6614ad4175a9d282d1b03b57f140f98a2962b4e0268aa015f8f942
|
4
|
+
data.tar.gz: 7ebabfddd0f3d8e8e05e1ef82d2389007c1419c727c5404d2ec2c1c09565a8bb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8d3221e4e5bd2120014ae11fe80a8e0a3b36af37b10aadd18f3bc6022e296efa47ea27d09572900bb6d6419fff64054caa6709e6b14e5182d32a188d0130b7f0
|
7
|
+
data.tar.gz: 5d7d996d53a12543de0830fdb8214e2ae39df4c19c649e81cd95ceb8d30d61f1f178b7a9a4f4e37adb591609eeac7e89d930cf5ffc24951c9cbfcb0b815c2a03
|
@@ -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
|
+
- 3.0.6
|
21
|
+
- 3.1.4
|
22
|
+
- 3.2.2
|
23
|
+
|
24
|
+
steps:
|
25
|
+
- uses: actions/checkout@v4
|
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,25 @@
|
|
1
|
+
=== Unreleased
|
2
|
+
|
3
|
+
=== 2.10.1 2023-12-01
|
4
|
+
* Parsing now handles VTIMEZONE blocks defined after their TZID is used in events and other components
|
5
|
+
|
6
|
+
=== 2.10.0 2023-11-01
|
7
|
+
* Add changelog metadata to gemspec - Juri Hahn
|
8
|
+
* Attempt to rescue timezone info when given a nonstandard tzid with no VTIMEZONE
|
9
|
+
* Move Values classes that shouldn't be directly used into Helpers module
|
10
|
+
|
11
|
+
=== 2.9.0 2023-08-11
|
12
|
+
* Always include the VALUE of Event URLs for improved compatibility - Sean Kelley
|
13
|
+
* Improved parse performance - Thomas Cannon
|
14
|
+
* Add helper methods for other Calendar method verbs
|
15
|
+
* bugfix: Require stringio before use in Parser - vwyu
|
16
|
+
|
17
|
+
=== 2.8.0 2022-07-10
|
18
|
+
* Fix compatibility with ActiveSupport 7 - Pat Allan
|
19
|
+
* Set default action of "DISPLAY" on alarms - Rikson
|
20
|
+
* Add license information to gemspec - Robert Reiz
|
21
|
+
* Support RFC7986 properties - Daniele Frisanco
|
22
|
+
|
1
23
|
=== 2.7.1 2021-03-14
|
2
24
|
* Recover from bad line-wrapping code that splits in the middle of Unicode code points
|
3
25
|
* Add a verbose option to the Parser to quiet some of the chattier log entries
|
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
|
@@ -19,6 +20,10 @@ variety of calendaring applications.
|
|
19
20
|
ActiveSupport is required for TimeWithZone support, but not required for general use.
|
20
21
|
EOM
|
21
22
|
|
23
|
+
s.metadata = {
|
24
|
+
'changelog_uri' => 'https://github.com/icalendar/icalendar/blob/main/History.txt'
|
25
|
+
}
|
26
|
+
|
22
27
|
s.files = `git ls-files`.split "\n"
|
23
28
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split "\n"
|
24
29
|
s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename f }
|
@@ -33,13 +38,13 @@ ActiveSupport is required for TimeWithZone support, but not required for general
|
|
33
38
|
|
34
39
|
# test with all groups of tzinfo dependencies
|
35
40
|
# tzinfo 2.x
|
36
|
-
|
37
|
-
|
38
|
-
# tzinfo 1.x
|
39
|
-
s.add_development_dependency 'activesupport', '~> 6.0'
|
40
|
-
s.add_development_dependency 'i18n', '~> 1.8'
|
41
|
-
s.add_development_dependency 'tzinfo', '~> 1.2'
|
41
|
+
s.add_development_dependency 'activesupport', '~> 7.1'
|
42
|
+
s.add_development_dependency 'tzinfo', '~> 2.0'
|
42
43
|
s.add_development_dependency 'tzinfo-data', '~> 1.2020'
|
44
|
+
# tzinfo 1.x
|
45
|
+
# s.add_development_dependency 'activesupport', '~> 6.0'
|
46
|
+
# s.add_development_dependency 'tzinfo', '~> 1.2'
|
47
|
+
# s.add_development_dependency 'tzinfo-data', '~> 1.2020'
|
43
48
|
# tzinfo 0.x
|
44
49
|
# s.add_development_dependency 'tzinfo', '~> 0.3'
|
45
50
|
# end tzinfo
|
@@ -47,4 +52,5 @@ ActiveSupport is required for TimeWithZone support, but not required for general
|
|
47
52
|
s.add_development_dependency 'timecop', '~> 0.9'
|
48
53
|
s.add_development_dependency 'rspec', '~> 3.8'
|
49
54
|
s.add_development_dependency 'simplecov', '~> 0.16'
|
55
|
+
s.add_development_dependency 'byebug'
|
50
56
|
end
|
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,18 +137,18 @@ 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
|
149
|
-
if mapped.is_a? Icalendar::Values::Array
|
150
|
+
mapped = map_property_value value, klass, true, new_property
|
151
|
+
if mapped.is_a? Icalendar::Values::Helpers::Array
|
150
152
|
instance_variable_set property_var, mapped.to_a.compact
|
151
153
|
else
|
152
154
|
instance_variable_set property_var, [mapped].compact
|
@@ -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::Helpers::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::Helpers::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,27 +186,32 @@ 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
|
-
params['x-tz-
|
214
|
+
params['x-tz-store'] = timezone_store
|
191
215
|
end
|
192
216
|
end
|
193
217
|
end
|
data/lib/icalendar/timezone.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'ice_cube'
|
2
4
|
|
3
5
|
module Icalendar
|
@@ -106,15 +108,17 @@ module Icalendar
|
|
106
108
|
|
107
109
|
def standard_for(local)
|
108
110
|
possible = standards.map do |std|
|
109
|
-
|
110
|
-
|
111
|
+
prev = std.previous_occurrence(local.to_time)
|
112
|
+
[prev, std] unless prev.nil?
|
113
|
+
end.compact
|
111
114
|
possible.sort_by(&:first).last
|
112
115
|
end
|
113
116
|
|
114
117
|
def daylight_for(local)
|
115
118
|
possible = daylights.map do |day|
|
116
|
-
|
117
|
-
|
119
|
+
prev = day.previous_occurrence(local.to_time)
|
120
|
+
[prev, day] unless prev.nil?
|
121
|
+
end.compact
|
118
122
|
possible.sort_by(&:first).last
|
119
123
|
end
|
120
124
|
end
|
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
|