icalendar 2.8.0 → 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 +1 -0
- data/History.txt +8 -0
- data/lib/icalendar/alarm.rb +2 -0
- data/lib/icalendar/calendar.rb +38 -0
- data/lib/icalendar/component.rb +8 -2
- data/lib/icalendar/downcased_hash.rb +2 -0
- data/lib/icalendar/event.rb +3 -1
- data/lib/icalendar/freebusy.rb +2 -0
- data/lib/icalendar/has_components.rb +7 -2
- data/lib/icalendar/has_properties.rb +2 -0
- data/lib/icalendar/journal.rb +2 -0
- data/lib/icalendar/logger.rb +2 -0
- data/lib/icalendar/marshable.rb +2 -0
- data/lib/icalendar/parser.rb +38 -14
- data/lib/icalendar/timezone.rb +2 -0
- data/lib/icalendar/timezone_store.rb +2 -0
- data/lib/icalendar/todo.rb +2 -0
- 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 -0
- 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 +2 -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/calendar_spec.rb +49 -0
- data/spec/event_spec.rb +4 -2
- metadata +2 -2
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
|
data/.github/workflows/main.yml
CHANGED
data/History.txt
CHANGED
@@ -1,3 +1,11 @@
|
|
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
|
+
|
1
9
|
=== 2.8.0 2022-07-10
|
2
10
|
* Fix compatibility with ActiveSupport 7 - Pat Allan
|
3
11
|
* Set default action of "DISPLAY" on alarms - Rikson
|
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
|
@@ -31,6 +33,42 @@ module Icalendar
|
|
31
33
|
|
32
34
|
def publish
|
33
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
|
34
72
|
end
|
35
73
|
|
36
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
|
@@ -24,7 +26,7 @@ module Icalendar
|
|
24
26
|
optional_single_property :status
|
25
27
|
optional_single_property :summary
|
26
28
|
optional_single_property :transp
|
27
|
-
optional_single_property :url, Icalendar::Values::Uri
|
29
|
+
optional_single_property :url, Icalendar::Values::Uri, true
|
28
30
|
optional_single_property :recurrence_id, Icalendar::Values::DateTime
|
29
31
|
|
30
32
|
optional_property :rrule, Icalendar::Values::Recur, true
|
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
|
data/lib/icalendar/journal.rb
CHANGED
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)
|
@@ -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
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
|
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/calendar_spec.rb
CHANGED
@@ -190,6 +190,55 @@ END:VCALENDAR
|
|
190
190
|
end
|
191
191
|
end
|
192
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
|
+
|
193
242
|
describe '.parse' do
|
194
243
|
let(:source) { File.read File.join(File.dirname(__FILE__), 'fixtures', 'bad_wrapping.ics') }
|
195
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
|