icalendar 2.4.1 → 2.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.github/workflows/main.yml +32 -0
- data/.gitignore +1 -0
- data/History.txt +39 -0
- data/{COPYING → LICENSE} +0 -0
- data/README.md +6 -22
- data/icalendar.gemspec +17 -18
- data/lib/icalendar/alarm.rb +1 -0
- data/lib/icalendar/calendar.rb +10 -0
- data/lib/icalendar/component.rb +14 -3
- data/lib/icalendar/event.rb +3 -0
- data/lib/icalendar/has_components.rb +17 -5
- data/lib/icalendar/has_properties.rb +27 -19
- data/lib/icalendar/journal.rb +2 -0
- data/lib/icalendar/marshable.rb +34 -0
- data/lib/icalendar/parser.rb +49 -16
- data/lib/icalendar/timezone.rb +68 -0
- data/lib/icalendar/timezone_store.rb +36 -0
- data/lib/icalendar/todo.rb +4 -1
- data/lib/icalendar/tzinfo.rb +2 -2
- data/lib/icalendar/values/array.rb +1 -2
- data/lib/icalendar/values/date.rb +2 -0
- data/lib/icalendar/values/date_or_date_time.rb +23 -11
- data/lib/icalendar/values/date_time.rb +4 -0
- data/lib/icalendar/values/time_with_zone.rb +23 -6
- data/lib/icalendar/values/uri.rb +2 -2
- data/lib/icalendar/values/utc_offset.rb +10 -2
- data/lib/icalendar/version.rb +1 -1
- data/lib/icalendar.rb +3 -1
- data/spec/alarm_spec.rb +7 -2
- data/spec/calendar_spec.rb +34 -0
- data/spec/event_spec.rb +24 -0
- data/spec/fixtures/bad_wrapping.ics +14 -0
- data/spec/fixtures/custom_component.ics +158 -0
- data/spec/fixtures/single_event.ics +1 -1
- data/spec/fixtures/single_event_bad_organizer.ics +22 -0
- data/spec/fixtures/single_event_organizer_parsed.ics +22 -0
- data/spec/fixtures/tzid_search.ics +31 -0
- data/spec/parser_spec.rb +31 -9
- data/spec/roundtrip_spec.rb +32 -9
- data/spec/timezone_spec.rb +48 -0
- data/spec/tzinfo_spec.rb +1 -1
- data/spec/values/date_or_date_time_spec.rb +10 -0
- metadata +65 -40
- data/.travis.yml +0 -18
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
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/.gitignore
CHANGED
data/History.txt
CHANGED
@@ -1,3 +1,42 @@
|
|
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
|
+
|
15
|
+
=== 2.6.1 2019-12-07
|
16
|
+
* Improve performance when generating large ICS files - Alex Balhatchet
|
17
|
+
|
18
|
+
=== 2.6.0 2019-11-26
|
19
|
+
* Improve performance for calculating timezone offsets - Justin Howard
|
20
|
+
* Make it possible to de/serialize with Marshal - Pawel Niewiadomski
|
21
|
+
* Avoid FrozenError when running with frozen_string_literal
|
22
|
+
* Update minimum Ruby version to supported versions
|
23
|
+
|
24
|
+
=== 2.5.3 2019-03-04
|
25
|
+
* Improve parsing performance - nehresma
|
26
|
+
* Support tzinfo 2.0 - Misty De Meo
|
27
|
+
|
28
|
+
=== 2.5.2 2018-12-08
|
29
|
+
* Remove usage of the global TimezoneStore instance, in favor of a local variable in the parser
|
30
|
+
* Deprecate TimezoneStore class methods
|
31
|
+
|
32
|
+
=== 2.5.1 2018-10-30
|
33
|
+
* Fix usage without ActiveSupport installed.
|
34
|
+
|
35
|
+
=== 2.5.0 2018-09-10
|
36
|
+
* Set timezone information from VTIMEZONE components in cases that ActiveSupport can't figure it out (or isn't installed)
|
37
|
+
* Prevent rewinding the Parser IO input during parsing - Niels Laukens
|
38
|
+
* Update tested/supported ruby versions & documentation updates.
|
39
|
+
|
1
40
|
=== 2.4.1 2016-09-03
|
2
41
|
* Fix parsing multiple calendars or components in same file - Patrick Schnetger
|
3
42
|
* Fix multi-byte folding bug - Niels Laukens
|
data/{COPYING → LICENSE}
RENAMED
File without changes
|
data/README.md
CHANGED
@@ -1,28 +1,11 @@
|
|
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>
|
8
8
|
|
9
|
-
2.x Status
|
10
|
-
---
|
11
|
-
|
12
|
-
iCalendar 2.0 is under active development, and can be followed in the
|
13
|
-
[master branch](https://github.com/icalendar/icalendar/tree/master).
|
14
|
-
|
15
|
-
iCalendar 1.x (currently the 1.x branch) will still survive for a
|
16
|
-
while, but will only be accepting bug fixes from this point forward
|
17
|
-
unless someone else wants to take over more active maintainership of
|
18
|
-
the 1.x series.
|
19
|
-
|
20
|
-
### 2.0 Goals ###
|
21
|
-
|
22
|
-
* Implements [RFC 5545](http://tools.ietf.org/html/rfc5545)
|
23
|
-
* More obvious access to parameters and values
|
24
|
-
* Cleaner & easier timezone support
|
25
|
-
|
26
9
|
### Upgrade from 1.x ###
|
27
10
|
|
28
11
|
Better documentation is still to come, but in the meantime the changes needed to move from 1.x to 2.0 are summarized by the [diff needed to update the README](https://github.com/icalendar/icalendar/commit/bc3701e004c915a250054030a9375d1e7618857f)
|
@@ -92,11 +75,11 @@ event.summary.ical_params #=> {'altrep' => 'http://my.language.net', 'language'
|
|
92
75
|
|
93
76
|
#### Support for Dates or DateTimes
|
94
77
|
|
95
|
-
Sometimes we don't care if an event's start or end are `Date` or `DateTime` objects. For this, we can use `DateOrDateTime.new(value)
|
78
|
+
Sometimes we don't care if an event's start or end are `Date` or `DateTime` objects. For this, we can use `DateOrDateTime.new(value)`. Calling `.call` on the returned `DateOrDateTime` will immediately return the underlying `Date` or `DateTime` object.
|
96
79
|
|
97
80
|
```ruby
|
98
81
|
event = cal.event do |e|
|
99
|
-
e.dtstart = Icalendar::Values::DateOrDateTime.new('20140924')
|
82
|
+
e.dtstart = Icalendar::Values::DateOrDateTime.new('20140924')
|
100
83
|
e.dtend = Icalendar::Values::DateOrDateTime.new('20140925').call
|
101
84
|
e.summary = 'This is an all-day event, because DateOrDateTime will return Dates'
|
102
85
|
end
|
@@ -132,7 +115,7 @@ cal.event do |e|
|
|
132
115
|
a.attendee = %w(mailto:me@my-domain.com mailto:me-too@my-domain.com) # one or more email recipients (required)
|
133
116
|
a.append_attendee "mailto:me-three@my-domain.com"
|
134
117
|
a.trigger = "-PT15M" # 15 minutes before
|
135
|
-
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)
|
136
119
|
end
|
137
120
|
|
138
121
|
e.alarm do |a|
|
@@ -237,7 +220,8 @@ end
|
|
237
220
|
iCalendar has some basic support for creating VTIMEZONE blocks from timezone information pulled from `tzinfo`.
|
238
221
|
You must require `tzinfo` support manually to take advantage.
|
239
222
|
|
240
|
-
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.
|
241
225
|
|
242
226
|
#### Example ####
|
243
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
|
@@ -16,11 +17,6 @@ for the generation and parsing of .ics files, which are used by a
|
|
16
17
|
variety of calendaring applications.
|
17
18
|
EOD
|
18
19
|
s.post_install_message = <<-EOM
|
19
|
-
HEADS UP! iCalendar 2.0 is not backwards-compatible with 1.x. Please see the README for the new syntax
|
20
|
-
|
21
|
-
HEADS UP! icalendar 2.2.0 switches to non-strict parsing as default. Please see the README if you
|
22
|
-
rely on strict parsing for information on how to enable it.
|
23
|
-
|
24
20
|
ActiveSupport is required for TimeWithZone support, but not required for general use.
|
25
21
|
EOM
|
26
22
|
|
@@ -29,24 +25,27 @@ ActiveSupport is required for TimeWithZone support, but not required for general
|
|
29
25
|
s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename f }
|
30
26
|
s.require_paths = ['lib']
|
31
27
|
|
32
|
-
s.required_ruby_version = '>=
|
28
|
+
s.required_ruby_version = '>= 2.4.0'
|
33
29
|
|
34
|
-
s.
|
35
|
-
s.add_development_dependency 'bundler', '~> 1.3'
|
30
|
+
s.add_dependency 'ice_cube', '~> 0.16'
|
36
31
|
|
37
|
-
|
32
|
+
s.add_development_dependency 'rake', '~> 13.0'
|
33
|
+
s.add_development_dependency 'bundler', '~> 2.0'
|
34
|
+
|
35
|
+
# test with all groups of tzinfo dependencies
|
36
|
+
# tzinfo 2.x
|
37
|
+
# s.add_development_dependency 'tzinfo', '~> 2.0'
|
38
|
+
# s.add_development_dependency 'tzinfo-data', '~> 1.2020'
|
38
39
|
# tzinfo 1.x
|
39
|
-
s.add_development_dependency '
|
40
|
-
s.add_development_dependency '
|
40
|
+
s.add_development_dependency 'activesupport', '~> 6.0'
|
41
|
+
s.add_development_dependency 'i18n', '~> 1.8'
|
42
|
+
s.add_development_dependency 'tzinfo', '~> 1.2'
|
43
|
+
s.add_development_dependency 'tzinfo-data', '~> 1.2020'
|
41
44
|
# tzinfo 0.x
|
42
45
|
# s.add_development_dependency 'tzinfo', '~> 0.3'
|
43
46
|
# end tzinfo
|
44
47
|
|
45
|
-
s.add_development_dependency '
|
46
|
-
|
47
|
-
s.add_development_dependency '
|
48
|
-
|
49
|
-
s.add_development_dependency 'timecop', '~> 0.7.0'
|
50
|
-
s.add_development_dependency 'rspec', '~> 3.0'
|
51
|
-
s.add_development_dependency 'simplecov', '~> 0.8'
|
48
|
+
s.add_development_dependency 'timecop', '~> 0.9'
|
49
|
+
s.add_development_dependency 'rspec', '~> 3.8'
|
50
|
+
s.add_development_dependency 'simplecov', '~> 0.16'
|
52
51
|
end
|
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)
|
@@ -71,6 +72,8 @@ module Icalendar
|
|
71
72
|
# than 75 octets, but you need to split between characters, not bytes.
|
72
73
|
# This is challanging with Unicode composing accents, for example.
|
73
74
|
|
75
|
+
return long_line if long_line.bytesize <= Icalendar::MAX_LINE_LENGTH
|
76
|
+
|
74
77
|
chars = long_line.scan(/\P{M}\p{M}*/u) # split in graphenes
|
75
78
|
folded = ['']
|
76
79
|
bytes = 0
|
@@ -99,6 +102,14 @@ module Icalendar
|
|
99
102
|
end
|
100
103
|
collection.empty? ? nil : collection.join.chomp("\r\n")
|
101
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
|
102
113
|
end
|
103
114
|
|
104
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
|
|
@@ -10,7 +10,7 @@ module Icalendar
|
|
10
10
|
end
|
11
11
|
|
12
12
|
def initialize(*args)
|
13
|
-
@custom_components = Hash.new
|
13
|
+
@custom_components = Hash.new
|
14
14
|
super
|
15
15
|
end
|
16
16
|
|
@@ -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
|
@@ -10,7 +10,7 @@ module Icalendar
|
|
10
10
|
end
|
11
11
|
|
12
12
|
def initialize(*args)
|
13
|
-
@custom_properties = Hash.new
|
13
|
+
@custom_properties = Hash.new
|
14
14
|
super
|
15
15
|
end
|
16
16
|
|
@@ -39,15 +39,19 @@ module Icalendar
|
|
39
39
|
end
|
40
40
|
|
41
41
|
def custom_property(property_name)
|
42
|
-
custom_properties[property_name.downcase]
|
42
|
+
custom_properties[property_name.downcase] || []
|
43
43
|
end
|
44
44
|
|
45
45
|
def append_custom_property(property_name, value)
|
46
46
|
property_name = property_name.downcase
|
47
|
-
if
|
48
|
-
|
47
|
+
if self.class.single_properties.include? property_name
|
48
|
+
send "#{property_name}=", value
|
49
|
+
elsif self.class.multiple_properties.include? property_name
|
50
|
+
send "append_#{property_name}", value
|
51
|
+
elsif value.is_a? Icalendar::Value
|
52
|
+
(custom_properties[property_name] ||= []) << value
|
49
53
|
else
|
50
|
-
custom_properties[property_name] << Icalendar::Values::Text.new(value)
|
54
|
+
(custom_properties[property_name] ||= []) << Icalendar::Values::Text.new(value)
|
51
55
|
end
|
52
56
|
end
|
53
57
|
|
@@ -100,29 +104,29 @@ module Icalendar
|
|
100
104
|
def required_property(prop, klass = Icalendar::Values::Text, validator = nil)
|
101
105
|
validator ||= ->(component, value) { !value.nil? }
|
102
106
|
self.required_properties[prop] = validator
|
103
|
-
single_property prop, klass
|
107
|
+
single_property prop, klass, false
|
104
108
|
end
|
105
109
|
|
106
110
|
def required_multi_property(prop, klass = Icalendar::Values::Text, validator = nil)
|
107
111
|
validator ||= ->(component, value) { !value.compact.empty? }
|
108
112
|
self.required_properties[prop] = validator
|
109
|
-
multi_property prop, klass
|
113
|
+
multi_property prop, klass, false
|
110
114
|
end
|
111
115
|
|
112
|
-
def optional_single_property(prop, klass = Icalendar::Values::Text)
|
113
|
-
single_property prop, klass
|
116
|
+
def optional_single_property(prop, klass = Icalendar::Values::Text, new_property = false)
|
117
|
+
single_property prop, klass, new_property
|
114
118
|
end
|
115
119
|
|
116
120
|
def mutually_exclusive_properties(*properties)
|
117
121
|
self.mutex_properties << properties
|
118
122
|
end
|
119
123
|
|
120
|
-
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)
|
121
125
|
self.suggested_single_properties << prop if suggested_single
|
122
|
-
multi_property prop, klass
|
126
|
+
multi_property prop, klass, new_property
|
123
127
|
end
|
124
128
|
|
125
|
-
def single_property(prop, klass)
|
129
|
+
def single_property(prop, klass, new_property)
|
126
130
|
self.single_properties << prop.to_s
|
127
131
|
self.default_property_types[prop.to_s] = klass
|
128
132
|
|
@@ -131,17 +135,17 @@ module Icalendar
|
|
131
135
|
end
|
132
136
|
|
133
137
|
define_method "#{prop}=" do |value|
|
134
|
-
instance_variable_set "@#{prop}", map_property_value(value, klass, false)
|
138
|
+
instance_variable_set "@#{prop}", map_property_value(value, klass, false, new_property)
|
135
139
|
end
|
136
140
|
end
|
137
141
|
|
138
|
-
def multi_property(prop, klass)
|
142
|
+
def multi_property(prop, klass, new_property)
|
139
143
|
self.multiple_properties << prop.to_s
|
140
144
|
self.default_property_types[prop.to_s] = klass
|
141
145
|
property_var = "@#{prop}"
|
142
146
|
|
143
147
|
define_method "#{prop}=" do |value|
|
144
|
-
mapped = map_property_value value, klass, true
|
148
|
+
mapped = map_property_value value, klass, true, new_property
|
145
149
|
if mapped.is_a? Icalendar::Values::Array
|
146
150
|
instance_variable_set property_var, mapped.to_a.compact
|
147
151
|
else
|
@@ -158,20 +162,24 @@ module Icalendar
|
|
158
162
|
end
|
159
163
|
|
160
164
|
define_method "append_#{prop}" do |value|
|
161
|
-
send(prop) << map_property_value(value, klass, true)
|
165
|
+
send(prop) << map_property_value(value, klass, true, new_property)
|
162
166
|
end
|
163
167
|
end
|
164
168
|
end
|
165
169
|
|
166
170
|
private
|
167
171
|
|
168
|
-
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
|
169
177
|
if value.nil? || value.is_a?(Icalendar::Value)
|
170
178
|
value
|
171
179
|
elsif value.is_a? ::Array
|
172
|
-
Icalendar::Values::Array.new value, klass,
|
180
|
+
Icalendar::Values::Array.new value, klass, params, {delimiter: (multi_valued ? ',' : ';')}
|
173
181
|
else
|
174
|
-
klass.new value
|
182
|
+
klass.new value, params
|
175
183
|
end
|
176
184
|
end
|
177
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'
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Icalendar
|
2
|
+
module Marshable
|
3
|
+
def self.included(base)
|
4
|
+
base.extend ClassMethods
|
5
|
+
end
|
6
|
+
|
7
|
+
def marshal_dump
|
8
|
+
instance_variables
|
9
|
+
.reject { |ivar| self.class.transient_variables.include?(ivar) }
|
10
|
+
.each_with_object({}) do |ivar, serialized|
|
11
|
+
|
12
|
+
serialized[ivar] = instance_variable_get(ivar)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def marshal_load(serialized)
|
17
|
+
serialized.each do |ivar, value|
|
18
|
+
unless self.class.transient_variables.include?(ivar)
|
19
|
+
instance_variable_set(ivar, value)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
module ClassMethods
|
25
|
+
def transient_variables
|
26
|
+
@transient_variables ||= [:@transient_variables]
|
27
|
+
end
|
28
|
+
|
29
|
+
def transient_variable(name)
|
30
|
+
transient_variables.push(name.to_sym)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/icalendar/parser.rb
CHANGED
@@ -1,10 +1,27 @@
|
|
1
|
+
require 'icalendar/timezone_store'
|
2
|
+
|
1
3
|
module Icalendar
|
2
4
|
|
3
5
|
class Parser
|
4
6
|
attr_writer :component_class
|
5
|
-
attr_reader :source, :strict
|
7
|
+
attr_reader :source, :strict, :timezone_store, :verbose
|
8
|
+
|
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
|
6
23
|
|
7
|
-
def initialize(source, strict = false)
|
24
|
+
def initialize(source, strict = false, verbose = false)
|
8
25
|
if source.respond_to? :gets
|
9
26
|
@source = source
|
10
27
|
elsif source.respond_to? :to_s
|
@@ -16,11 +33,11 @@ module Icalendar
|
|
16
33
|
end
|
17
34
|
read_in_data
|
18
35
|
@strict = strict
|
36
|
+
@verbose = verbose
|
37
|
+
@timezone_store = TimezoneStore.new
|
19
38
|
end
|
20
39
|
|
21
40
|
def parse
|
22
|
-
source.rewind
|
23
|
-
read_in_data
|
24
41
|
components = []
|
25
42
|
while (fields = next_fields)
|
26
43
|
component = component_class.new
|
@@ -33,7 +50,7 @@ module Icalendar
|
|
33
50
|
|
34
51
|
def parse_property(component, fields = nil)
|
35
52
|
fields = next_fields if fields.nil?
|
36
|
-
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]
|
37
54
|
multi_property = component.class.multiple_properties.include? prop_name
|
38
55
|
prop_value = wrap_property_value component, fields, multi_property
|
39
56
|
begin
|
@@ -48,7 +65,7 @@ module Icalendar
|
|
48
65
|
Icalendar.logger.error "No method \"#{method_name}\" for component #{component}"
|
49
66
|
raise nme
|
50
67
|
else
|
51
|
-
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?
|
52
69
|
component.append_custom_property prop_name, prop_value
|
53
70
|
end
|
54
71
|
end
|
@@ -81,8 +98,8 @@ module Icalendar
|
|
81
98
|
if !fields[:params]['value'].nil?
|
82
99
|
klass_name = fields[:params].delete('value').first
|
83
100
|
unless klass_name.upcase == klass.value_type
|
84
|
-
klass_name = klass_name.downcase.gsub(/(?:\A|-)(.)/) { |m| m[-1].upcase }
|
85
|
-
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)
|
86
103
|
end
|
87
104
|
end
|
88
105
|
klass
|
@@ -92,6 +109,10 @@ module Icalendar
|
|
92
109
|
!!@strict
|
93
110
|
end
|
94
111
|
|
112
|
+
def verbose?
|
113
|
+
@verbose
|
114
|
+
end
|
115
|
+
|
95
116
|
private
|
96
117
|
|
97
118
|
def component_class
|
@@ -101,16 +122,18 @@ module Icalendar
|
|
101
122
|
def parse_component(component)
|
102
123
|
while (fields = next_fields)
|
103
124
|
if fields[:name] == 'end'
|
125
|
+
klass_name = fields[:value].gsub(/\AV/, '').downcase.capitalize
|
126
|
+
timezone_store.store(component) if klass_name == 'Timezone'
|
104
127
|
break
|
105
128
|
elsif fields[:name] == 'begin'
|
106
|
-
klass_name = fields[:value].gsub(/\AV/, '').downcase.capitalize
|
129
|
+
klass_name = fields[:value].gsub(/\AV/, '').gsub("-", "_").downcase.capitalize
|
107
130
|
Icalendar.logger.debug "Adding component #{klass_name}"
|
108
|
-
if
|
109
|
-
component.add_component parse_component(
|
110
|
-
elsif
|
111
|
-
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)
|
112
135
|
else
|
113
|
-
component.
|
136
|
+
component.add_custom_component klass_name, parse_component(Component.new klass_name.downcase, fields[:value])
|
114
137
|
end
|
115
138
|
else
|
116
139
|
parse_property component, fields
|
@@ -160,10 +183,20 @@ module Icalendar
|
|
160
183
|
param_name = match[0].downcase
|
161
184
|
params[param_name] ||= []
|
162
185
|
match[1].scan %r{#{PVALUE}} do |param_value|
|
163
|
-
|
186
|
+
if param_value.size > 0
|
187
|
+
param_value = param_value.gsub(/\A"|"\z/, '')
|
188
|
+
params[param_name] << param_value
|
189
|
+
if param_name == 'tzid'
|
190
|
+
params['x-tz-info'] = timezone_store.retrieve param_value
|
191
|
+
end
|
192
|
+
end
|
164
193
|
end
|
165
194
|
end
|
166
|
-
|
195
|
+
# Building the string to send to the logger is expensive.
|
196
|
+
# Only do it if the logger is at the right log level.
|
197
|
+
if ::Logger::DEBUG >= Icalendar.logger.level
|
198
|
+
Icalendar.logger.debug "Found fields: #{parts.inspect} with params: #{params.inspect}"
|
199
|
+
end
|
167
200
|
{
|
168
201
|
name: parts[:name].downcase.gsub('-', '_'),
|
169
202
|
params: params,
|