icalendar 1.5.4 → 2.0.0.beta.1

Sign up to get free protection for your applications and to get access to all the features.
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