icalendar 1.5.4 → 2.0.0.beta.1

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.
Files changed (100) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +1 -1
  3. data/.rspec +2 -0
  4. data/.travis.yml +1 -2
  5. data/History.txt +2 -7
  6. data/README.md +82 -107
  7. data/Rakefile +6 -7
  8. data/icalendar.gemspec +10 -9
  9. data/lib/icalendar.rb +17 -33
  10. data/lib/icalendar/alarm.rb +35 -0
  11. data/lib/icalendar/calendar.rb +17 -100
  12. data/lib/icalendar/component.rb +41 -403
  13. data/lib/icalendar/event.rb +51 -0
  14. data/lib/icalendar/freebusy.rb +27 -0
  15. data/lib/icalendar/has_components.rb +83 -0
  16. data/lib/icalendar/has_properties.rb +156 -0
  17. data/lib/icalendar/journal.rb +39 -0
  18. data/lib/icalendar/parser.rb +75 -403
  19. data/lib/icalendar/timezone.rb +53 -0
  20. data/lib/icalendar/todo.rb +52 -0
  21. data/lib/icalendar/tzinfo.rb +30 -30
  22. data/lib/icalendar/value.rb +80 -0
  23. data/lib/icalendar/values/array.rb +43 -0
  24. data/lib/icalendar/values/binary.rb +31 -0
  25. data/lib/icalendar/values/boolean.rb +17 -0
  26. data/lib/icalendar/values/cal_address.rb +8 -0
  27. data/lib/icalendar/values/date.rb +26 -0
  28. data/lib/icalendar/values/date_time.rb +34 -0
  29. data/lib/icalendar/values/duration.rb +48 -0
  30. data/lib/icalendar/values/float.rb +17 -0
  31. data/lib/icalendar/values/integer.rb +17 -0
  32. data/lib/icalendar/values/period.rb +46 -0
  33. data/lib/icalendar/values/recur.rb +63 -0
  34. data/lib/icalendar/values/text.rb +26 -0
  35. data/lib/icalendar/values/time.rb +34 -0
  36. data/lib/icalendar/values/time_with_zone.rb +31 -0
  37. data/lib/icalendar/values/uri.rb +19 -0
  38. data/lib/icalendar/values/utc_offset.rb +39 -0
  39. data/lib/icalendar/version.rb +5 -0
  40. data/spec/alarm_spec.rb +108 -0
  41. data/spec/calendar_spec.rb +167 -0
  42. data/spec/event_spec.rb +108 -0
  43. data/{test/fixtures/folding.ics → spec/fixtures/nondefault_values.ics} +2 -2
  44. data/{test → spec}/fixtures/single_event.ics +11 -14
  45. data/spec/fixtures/timezone.ics +35 -0
  46. data/spec/freebusy_spec.rb +7 -0
  47. data/spec/journal_spec.rb +7 -0
  48. data/spec/parser_spec.rb +26 -0
  49. data/spec/roundtrip_spec.rb +40 -0
  50. data/spec/spec_helper.rb +25 -0
  51. data/spec/timezone_spec.rb +31 -0
  52. data/spec/todo_spec.rb +24 -0
  53. data/spec/tzinfo_spec.rb +85 -0
  54. data/spec/values/date_time_spec.rb +80 -0
  55. data/spec/values/duration_spec.rb +67 -0
  56. data/spec/values/period_spec.rb +47 -0
  57. data/spec/values/recur_spec.rb +47 -0
  58. data/spec/values/text_spec.rb +72 -0
  59. data/spec/values/utc_offset_spec.rb +41 -0
  60. metadata +129 -88
  61. data/GPL +0 -340
  62. data/examples/create_cal.rb +0 -45
  63. data/examples/parse_cal.rb +0 -20
  64. data/examples/single_event.ics +0 -18
  65. data/lib/hash_attrs.rb +0 -34
  66. data/lib/icalendar/base.rb +0 -47
  67. data/lib/icalendar/component/alarm.rb +0 -47
  68. data/lib/icalendar/component/event.rb +0 -131
  69. data/lib/icalendar/component/freebusy.rb +0 -38
  70. data/lib/icalendar/component/journal.rb +0 -60
  71. data/lib/icalendar/component/timezone.rb +0 -91
  72. data/lib/icalendar/component/todo.rb +0 -64
  73. data/lib/icalendar/conversions.rb +0 -107
  74. data/lib/icalendar/helpers.rb +0 -109
  75. data/lib/icalendar/parameter.rb +0 -33
  76. data/lib/icalendar/rrule.rb +0 -133
  77. data/lib/meta.rb +0 -32
  78. data/script/console +0 -10
  79. data/script/recur1.ics +0 -38
  80. data/script/tryit.rb +0 -13
  81. data/test/component/test_event.rb +0 -253
  82. data/test/component/test_timezone.rb +0 -74
  83. data/test/component/test_todo.rb +0 -31
  84. data/test/fixtures/life.ics +0 -46
  85. data/test/fixtures/nonstandard.ics +0 -25
  86. data/test/fixtures/simplecal.ics +0 -119
  87. data/test/interactive.rb +0 -17
  88. data/test/read_write.rb +0 -23
  89. data/test/test_calendar.rb +0 -167
  90. data/test/test_component.rb +0 -102
  91. data/test/test_conversions.rb +0 -104
  92. data/test/test_helper.rb +0 -7
  93. data/test/test_parameter.rb +0 -91
  94. data/test/test_parser.rb +0 -100
  95. data/test/test_tzinfo.rb +0 -83
  96. data/website/index.html +0 -70
  97. data/website/index.txt +0 -38
  98. data/website/javascripts/rounded_corners_lite.inc.js +0 -285
  99. data/website/stylesheets/screen.css +0 -159
  100. data/website/template.html.erb +0 -50
@@ -0,0 +1,48 @@
1
+ require 'ostruct'
2
+
3
+ module Icalendar
4
+ module Values
5
+
6
+ class Duration < Value
7
+
8
+ def initialize(value, params = {})
9
+ super OpenStruct.new(parse_fields value), params
10
+ end
11
+
12
+ def past?
13
+ value.past
14
+ end
15
+
16
+ def value_ical
17
+ return "#{'-' if past?}P#{weeks}W" if weeks > 0
18
+ builder = []
19
+ builder << '-' if past?
20
+ builder << 'P'
21
+ builder << "#{days}D" if days > 0
22
+ builder << 'T' if time?
23
+ builder << "#{hours}H" if hours > 0
24
+ builder << "#{minutes}M" if minutes > 0
25
+ builder << "#{seconds}S" if seconds > 0
26
+ builder.join
27
+ end
28
+
29
+ private
30
+
31
+ def time?
32
+ hours > 0 || minutes > 0 || seconds > 0
33
+ end
34
+
35
+ def parse_fields(value)
36
+ {
37
+ past: (value =~ /\A([+-])P/ ? $1 == '-' : false),
38
+ weeks: (value =~ /(\d+)W/ ? $1.to_i : 0),
39
+ days: (value =~ /(\d+)D/ ? $1.to_i : 0),
40
+ hours: (value =~ /(\d+)H/ ? $1.to_i : 0),
41
+ minutes: (value =~ /(\d+)M/ ? $1.to_i : 0),
42
+ seconds: (value =~ /(\d+)S/ ? $1.to_i : 0)
43
+ }
44
+ end
45
+ end
46
+
47
+ end
48
+ end
@@ -0,0 +1,17 @@
1
+ module Icalendar
2
+ module Values
3
+
4
+ class Float < Value
5
+
6
+ def initialize(value, params = {})
7
+ super value.to_f, params
8
+ end
9
+
10
+ def value_ical
11
+ value.to_s
12
+ end
13
+
14
+ end
15
+
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ module Icalendar
2
+ module Values
3
+
4
+ class Integer < Value
5
+
6
+ def initialize(value, params = {})
7
+ super value.to_i, params
8
+ end
9
+
10
+ def value_ical
11
+ value.to_s
12
+ end
13
+
14
+ end
15
+
16
+ end
17
+ end
@@ -0,0 +1,46 @@
1
+ module Icalendar
2
+ module Values
3
+
4
+ class Period < Value
5
+
6
+ def initialize(value, params = {})
7
+ parts = value.split '/'
8
+ period_start = Icalendar::Values::DateTime.new parts.first
9
+ if parts.last =~ /\A[+-]?P.+\z/
10
+ period_end = Icalendar::Values::Duration.new parts.last
11
+ else
12
+ period_end = Icalendar::Values::DateTime.new parts.last
13
+ end
14
+ super [period_start, period_end], params
15
+ end
16
+
17
+ def value_ical
18
+ value.map { |v| v.value_ical }.join '/'
19
+ end
20
+
21
+ def period_start
22
+ first
23
+ end
24
+
25
+ def period_start=(v)
26
+ value[0] = v.is_a?(Icalendar::Values::DateTime) ? v : Icalendar::Values::DateTime.new(v)
27
+ end
28
+
29
+ def explicit_end
30
+ last.is_a?(Icalendar::Values::DateTime) ? last : nil
31
+ end
32
+
33
+ def explicit_end=(v)
34
+ value[1] = v.is_a?(Icalendar::Values::DateTime) ? v : Icalendar::Values::DateTime.new(v)
35
+ end
36
+
37
+ def duration
38
+ last.is_a?(Icalendar::Values::Duration) ? last : nil
39
+ end
40
+
41
+ def duration=(v)
42
+ value[1] = v.is_a?(Icalendar::Values::Duration) ? v : Icalendar::Values::Duration.new(v)
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,63 @@
1
+ require 'ostruct'
2
+
3
+ module Icalendar
4
+ module Values
5
+
6
+ class Recur < Value
7
+ NUM_LIST = '\d{1,2}(?:,\d{1,2})*'
8
+ DAYNAME = 'SU|MO|TU|WE|TH|FR|SA'
9
+ WEEKDAY = "(?:[+-]?\\d{1,2})?(?:#{DAYNAME})"
10
+ MONTHDAY = '[+-]?\d{1,2}'
11
+ YEARDAY = '[+-]?\d{1,3}'
12
+
13
+ def initialize(value, params = {})
14
+ super OpenStruct.new(parse_fields value), params
15
+ end
16
+
17
+ def valid?
18
+ return false if frequency.nil?
19
+ return false if !self.until.nil? && !count.nil?
20
+ true
21
+ end
22
+
23
+ def value_ical
24
+ builder = ["FREQ=#{frequency}"]
25
+ builder << "UNTIL=#{self.until}" unless self.until.nil?
26
+ builder << "COUNT=#{count}" unless count.nil?
27
+ builder << "INTERVAL=#{interval}" unless interval.nil?
28
+ builder << "BYSECOND=#{by_second.join ','}" unless by_second.nil?
29
+ builder << "BYMINUTE=#{by_minute.join ','}" unless by_minute.nil?
30
+ builder << "BYHOUR=#{by_hour.join ','}" unless by_hour.nil?
31
+ builder << "BYDAY=#{by_day.join ','}" unless by_day.nil?
32
+ builder << "BYMONTHDAY=#{by_month_day.join ','}" unless by_month_day.nil?
33
+ builder << "BYYEARDAY=#{by_year_day.join ','}" unless by_year_day.nil?
34
+ builder << "BYWEEKNO=#{by_week_number.join ','}" unless by_week_number.nil?
35
+ builder << "BYMONTH=#{by_month.join ','}" unless by_month.nil?
36
+ builder << "BYSETPOS=#{by_set_position.join ','}" unless by_set_position.nil?
37
+ builder << "WKST=#{week_start}" unless week_start.nil?
38
+ builder.join ';'
39
+ end
40
+
41
+ private
42
+
43
+ def parse_fields(value)
44
+ {
45
+ frequency: (value =~ /FREQ=(SECONDLY|MINUTELY|HOURLY|DAILY|WEEKLY|MONTHLY|YEARLY)/i ? $1.upcase : nil),
46
+ until: (value =~ /UNTIL=([^;]*)/i ? $1 : nil),
47
+ count: (value =~ /COUNT=(\d+)/i ? $1.to_i : nil),
48
+ interval: (value =~ /INTERVAL=(\d+)/i ? $1.to_i : nil),
49
+ by_second: (value =~ /BYSECOND=(#{NUM_LIST})(?:;|\z)/i ? $1.split(',').map { |i| i.to_i } : nil),
50
+ by_minute: (value =~ /BYMINUTE=(#{NUM_LIST})(?:;|\z)/i ? $1.split(',').map { |i| i.to_i } : nil),
51
+ by_hour: (value =~ /BYHOUR=(#{NUM_LIST})(?:;|\z)/i ? $1.split(',').map { |i| i.to_i } : nil),
52
+ by_day: (value =~ /BYDAY=(#{WEEKDAY}(?:,#{WEEKDAY})*)(?:;|\z)/i ? $1.split(',') : nil),
53
+ by_month_day: (value =~ /BYMONTHDAY=(#{MONTHDAY}(?:,#{MONTHDAY})*)(?:;|\z)/i ? $1.split(',') : nil),
54
+ by_year_day: (value =~ /BYYEARDAY=(#{YEARDAY}(?:,#{YEARDAY})*)(?:;|\z)/i ? $1.split(',') : nil),
55
+ by_week_number: (value =~ /BYWEEKNO=(#{MONTHDAY}(?:,#{MONTHDAY})*)(?:;|\z)/i ? $1.split(',') : nil),
56
+ by_month: (value =~ /BYMONTH=(#{NUM_LIST})(?:;|\z)/i ? $1.split(',').map { |i| i.to_i } : nil),
57
+ by_set_position: (value =~ /BYSETPOS=(#{YEARDAY}(?:,#{YEARDAY})*)(?:;|\z)/i ? $1.split(',') : nil),
58
+ week_start: (value =~ /WKST=(#{DAYNAME})/i ? $1.upcase : nil)
59
+ }
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,26 @@
1
+ module Icalendar
2
+ module Values
3
+
4
+ class Text < Value
5
+
6
+ def initialize(value, params = {})
7
+ value = value.gsub '\n', "\n"
8
+ value.gsub! '\,', ','
9
+ value.gsub! '\;', ';'
10
+ value.gsub!('\\\\') { '\\' }
11
+ super value, params
12
+ end
13
+
14
+ def value_ical
15
+ value.dup.tap do |v|
16
+ v.gsub!('\\') { '\\\\' }
17
+ v.gsub! ';', '\;'
18
+ v.gsub! ',', '\,'
19
+ v.gsub! /\r?\n/, '\n'
20
+ end
21
+ end
22
+
23
+ end
24
+
25
+ end
26
+ end
@@ -0,0 +1,34 @@
1
+ require 'date'
2
+ require_relative 'time_with_zone'
3
+
4
+ module Icalendar
5
+ module Values
6
+
7
+ class Time < Value
8
+ include TimeWithZone
9
+
10
+ FORMAT = '%H%M%S'
11
+
12
+ def initialize(value, params = {})
13
+ if value.is_a? String
14
+ params['tzid'] = 'UTC' if value.end_with? 'Z'
15
+ super ::DateTime.strptime(value, FORMAT).to_time, params
16
+ elsif value.respond_to? :to_time
17
+ super value.to_time, params
18
+ else
19
+ super
20
+ end
21
+ end
22
+
23
+ def value_ical
24
+ if tz_utc
25
+ "#{strftime FORMAT}Z"
26
+ else
27
+ strftime FORMAT
28
+ end
29
+ end
30
+
31
+ end
32
+
33
+ end
34
+ end
@@ -0,0 +1,31 @@
1
+ begin
2
+ require 'active_support/time'
3
+ rescue LoadError
4
+ $stderr.puts "Info: No TimeWithZone support"
5
+ end
6
+
7
+ module Icalendar
8
+ module Values
9
+ module TimeWithZone
10
+ attr_reader :tz_utc
11
+
12
+ def initialize(value, params = {})
13
+ @tz_utc = params['tzid'] == 'UTC'
14
+
15
+ if defined?(ActiveSupport) && !params['tzid'].nil?
16
+ tzid = params['tzid'].is_a?(::Array) ? params['tzid'].first : params['tzid']
17
+ zone = ActiveSupport::TimeZone[tzid]
18
+ value = ActiveSupport::TimeWithZone.new nil, zone, value unless zone.nil?
19
+ super value, params
20
+ else
21
+ super
22
+ end
23
+ end
24
+
25
+ def params_ical
26
+ ical_params.delete 'tzid' if tz_utc
27
+ super
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,19 @@
1
+ require 'uri'
2
+
3
+ module Icalendar
4
+ module Values
5
+
6
+ class Uri < Value
7
+
8
+ def initialize(value, params = {})
9
+ parsed = URI.parse value rescue value
10
+ super parsed, params
11
+ end
12
+
13
+ def value_ical
14
+ value.to_s
15
+ end
16
+ end
17
+
18
+ end
19
+ end
@@ -0,0 +1,39 @@
1
+ require 'ostruct'
2
+
3
+ module Icalendar
4
+ module Values
5
+
6
+ class UtcOffset < Value
7
+
8
+ def initialize(value, params = {})
9
+ value = OpenStruct.new parse_fields(value)
10
+ super value, params
11
+ end
12
+
13
+ def behind?
14
+ return false if zero_offset?
15
+ value.behind
16
+ end
17
+
18
+ def value_ical
19
+ "#{behind? ? '-' : '+'}#{'%02d' % hours}#{'%02d' % minutes}#{'%02d' % seconds if seconds > 0}"
20
+ end
21
+
22
+ private
23
+
24
+ def zero_offset?
25
+ hours == 0 && minutes == 0 && seconds == 0
26
+ end
27
+
28
+ def parse_fields(value)
29
+ md = /\A(?<behind>[+-])(?<hours>\d{2})(?<minutes>\d{2})(?<seconds>\d{2})?\z/.match value
30
+ {
31
+ behind: (md[:behind] == '-'),
32
+ hours: md[:hours].to_i,
33
+ minutes: md[:minutes].to_i,
34
+ seconds: md[:seconds].to_i
35
+ }
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,5 @@
1
+ module Icalendar
2
+
3
+ VERSION = '2.0.0.beta.1'
4
+
5
+ end
@@ -0,0 +1,108 @@
1
+ require 'spec_helper'
2
+
3
+ describe Icalendar::Alarm do
4
+
5
+ # currently no behavior in Alarm not tested other places
6
+ describe '#valid?' do
7
+ subject do
8
+ described_class.new.tap do |a|
9
+ a.action = 'AUDIO'
10
+ a.trigger = Time.now.utc.to_datetime
11
+ end
12
+ end
13
+ context 'neither duration or repeat is set' do
14
+ it { should be_valid }
15
+ end
16
+ context 'both duration and repeat are set' do
17
+ before(:each) do
18
+ subject.duration = 'PT15M'
19
+ subject.repeat = 4
20
+ end
21
+ it { should be_valid }
22
+ end
23
+ context 'only duration is set' do
24
+ before(:each) { subject.duration = 'PT15M' }
25
+ it { should_not be_valid }
26
+ end
27
+ context 'only repeat is set' do
28
+ before(:each) { subject.repeat = 4 }
29
+ it { should_not be_valid }
30
+ end
31
+
32
+ context 'display action' do
33
+ before(:each) { subject.action = 'DISPLAY' }
34
+ it 'requires description' do
35
+ subject.should_not be_valid
36
+ subject.description = 'Display Text'
37
+ subject.should be_valid
38
+ end
39
+ end
40
+
41
+ context 'email action' do
42
+ before(:each) { subject.action = 'EMAIL' }
43
+ context 'requires subject and body' do
44
+ before(:each) { subject.attendee = ['mailto:test@email.com'] }
45
+ it 'requires description' do
46
+ subject.summary = 'Email subject'
47
+ subject.should_not be_valid
48
+ subject.description = 'Email Body'
49
+ subject.should be_valid
50
+ end
51
+ it 'requires summary' do
52
+ subject.description = 'Email body'
53
+ subject.should_not be_valid
54
+ subject.summary = 'Email subject'
55
+ subject.should be_valid
56
+ end
57
+ end
58
+ context 'attendees are required' do
59
+ before(:each) do
60
+ subject.summary = 'subject'
61
+ subject.description = 'body'
62
+ end
63
+
64
+ it 'must be present' do
65
+ subject.attendee = nil
66
+ subject.should_not be_valid
67
+ end
68
+
69
+ it 'can be single' do
70
+ subject.attendee << 'mailto:test@email.com'
71
+ subject.should be_valid
72
+ end
73
+
74
+ it 'can be multi' do
75
+ subject.attendee << 'mailto:test@email.com'
76
+ subject.attendee << 'mailto:email@test.com'
77
+ subject.should be_valid
78
+ end
79
+ end
80
+ end
81
+
82
+ context 'strict validations check parent' do
83
+ subject do
84
+ described_class.new.tap do |a|
85
+ a.action = 'AUDIO'
86
+ a.trigger = Time.now.utc.to_datetime
87
+ end
88
+ end
89
+ specify { subject.valid?(true).should be_true }
90
+ context 'with parent' do
91
+ before(:each) { subject.parent = parent }
92
+ context 'event' do
93
+ let(:parent) { Icalendar::Event.new }
94
+ specify { subject.valid?(true).should be_true }
95
+ end
96
+ context 'todo' do
97
+ let(:parent) { Icalendar::Todo.new }
98
+ specify { subject.valid?(true).should be_true }
99
+ end
100
+ context 'journal' do
101
+ let(:parent) { Icalendar::Journal.new }
102
+ specify { subject.valid?(true).should be_false }
103
+ end
104
+ end
105
+ end
106
+ end
107
+
108
+ end