icalendar 2.4.1 → 2.8.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 +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
|
-
[](https://travis-ci.com/icalendar/icalendar)
|
|
5
5
|
[](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,
|