icalendar 2.8.0 → 2.9.0
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 +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
|