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