ri_cal 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (130) hide show
  1. data/History.txt +45 -0
  2. data/Manifest.txt +129 -0
  3. data/README.txt +394 -0
  4. data/Rakefile +31 -0
  5. data/bin/ri_cal +8 -0
  6. data/component_attributes/alarm.yml +10 -0
  7. data/component_attributes/calendar.yml +4 -0
  8. data/component_attributes/component_property_defs.yml +180 -0
  9. data/component_attributes/event.yml +45 -0
  10. data/component_attributes/freebusy.yml +16 -0
  11. data/component_attributes/journal.yml +35 -0
  12. data/component_attributes/timezone.yml +3 -0
  13. data/component_attributes/timezone_period.yml +11 -0
  14. data/component_attributes/todo.yml +46 -0
  15. data/copyrights.txt +1 -0
  16. data/docs/draft-ietf-calsify-2446bis-08.txt +7280 -0
  17. data/docs/draft-ietf-calsify-rfc2445bis-09.txt +10416 -0
  18. data/docs/incrementers.txt +7 -0
  19. data/docs/rfc2445.pdf +0 -0
  20. data/lib/ri_cal.rb +144 -0
  21. data/lib/ri_cal/component.rb +247 -0
  22. data/lib/ri_cal/component/alarm.rb +21 -0
  23. data/lib/ri_cal/component/calendar.rb +219 -0
  24. data/lib/ri_cal/component/event.rb +60 -0
  25. data/lib/ri_cal/component/freebusy.rb +18 -0
  26. data/lib/ri_cal/component/journal.rb +30 -0
  27. data/lib/ri_cal/component/t_z_info_timezone.rb +123 -0
  28. data/lib/ri_cal/component/timezone.rb +196 -0
  29. data/lib/ri_cal/component/timezone/daylight_period.rb +25 -0
  30. data/lib/ri_cal/component/timezone/standard_period.rb +23 -0
  31. data/lib/ri_cal/component/timezone/timezone_period.rb +53 -0
  32. data/lib/ri_cal/component/todo.rb +43 -0
  33. data/lib/ri_cal/core_extensions.rb +6 -0
  34. data/lib/ri_cal/core_extensions/array.rb +7 -0
  35. data/lib/ri_cal/core_extensions/array/conversions.rb +15 -0
  36. data/lib/ri_cal/core_extensions/date.rb +13 -0
  37. data/lib/ri_cal/core_extensions/date/conversions.rb +61 -0
  38. data/lib/ri_cal/core_extensions/date_time.rb +15 -0
  39. data/lib/ri_cal/core_extensions/date_time/conversions.rb +50 -0
  40. data/lib/ri_cal/core_extensions/object.rb +8 -0
  41. data/lib/ri_cal/core_extensions/object/conversions.rb +20 -0
  42. data/lib/ri_cal/core_extensions/string.rb +8 -0
  43. data/lib/ri_cal/core_extensions/string/conversions.rb +63 -0
  44. data/lib/ri_cal/core_extensions/time.rb +13 -0
  45. data/lib/ri_cal/core_extensions/time/calculations.rb +153 -0
  46. data/lib/ri_cal/core_extensions/time/conversions.rb +61 -0
  47. data/lib/ri_cal/core_extensions/time/tzid_access.rb +50 -0
  48. data/lib/ri_cal/core_extensions/time/week_day_predicates.rb +88 -0
  49. data/lib/ri_cal/floating_timezone.rb +32 -0
  50. data/lib/ri_cal/invalid_property_value.rb +8 -0
  51. data/lib/ri_cal/invalid_timezone_identifer.rb +20 -0
  52. data/lib/ri_cal/occurrence_enumerator.rb +206 -0
  53. data/lib/ri_cal/occurrence_period.rb +17 -0
  54. data/lib/ri_cal/parser.rb +138 -0
  55. data/lib/ri_cal/properties/alarm.rb +390 -0
  56. data/lib/ri_cal/properties/calendar.rb +164 -0
  57. data/lib/ri_cal/properties/event.rb +1526 -0
  58. data/lib/ri_cal/properties/freebusy.rb +594 -0
  59. data/lib/ri_cal/properties/journal.rb +1240 -0
  60. data/lib/ri_cal/properties/timezone.rb +151 -0
  61. data/lib/ri_cal/properties/timezone_period.rb +416 -0
  62. data/lib/ri_cal/properties/todo.rb +1562 -0
  63. data/lib/ri_cal/property_value.rb +149 -0
  64. data/lib/ri_cal/property_value/array.rb +27 -0
  65. data/lib/ri_cal/property_value/cal_address.rb +11 -0
  66. data/lib/ri_cal/property_value/date.rb +175 -0
  67. data/lib/ri_cal/property_value/date_time.rb +335 -0
  68. data/lib/ri_cal/property_value/date_time/additive_methods.rb +44 -0
  69. data/lib/ri_cal/property_value/date_time/time_machine.rb +181 -0
  70. data/lib/ri_cal/property_value/date_time/timezone_support.rb +96 -0
  71. data/lib/ri_cal/property_value/duration.rb +110 -0
  72. data/lib/ri_cal/property_value/geo.rb +11 -0
  73. data/lib/ri_cal/property_value/integer.rb +12 -0
  74. data/lib/ri_cal/property_value/occurrence_list.rb +144 -0
  75. data/lib/ri_cal/property_value/period.rb +82 -0
  76. data/lib/ri_cal/property_value/recurrence_rule.rb +145 -0
  77. data/lib/ri_cal/property_value/recurrence_rule/enumeration_support_methods.rb +97 -0
  78. data/lib/ri_cal/property_value/recurrence_rule/enumerator.rb +79 -0
  79. data/lib/ri_cal/property_value/recurrence_rule/initialization_methods.rb +148 -0
  80. data/lib/ri_cal/property_value/recurrence_rule/negative_setpos_enumerator.rb +53 -0
  81. data/lib/ri_cal/property_value/recurrence_rule/numbered_span.rb +31 -0
  82. data/lib/ri_cal/property_value/recurrence_rule/occurence_incrementer.rb +793 -0
  83. data/lib/ri_cal/property_value/recurrence_rule/recurring_day.rb +131 -0
  84. data/lib/ri_cal/property_value/recurrence_rule/recurring_month_day.rb +60 -0
  85. data/lib/ri_cal/property_value/recurrence_rule/recurring_numbered_week.rb +33 -0
  86. data/lib/ri_cal/property_value/recurrence_rule/recurring_year_day.rb +49 -0
  87. data/lib/ri_cal/property_value/recurrence_rule/validations.rb +125 -0
  88. data/lib/ri_cal/property_value/text.rb +40 -0
  89. data/lib/ri_cal/property_value/uri.rb +11 -0
  90. data/lib/ri_cal/property_value/utc_offset.rb +33 -0
  91. data/lib/ri_cal/required_timezones.rb +55 -0
  92. data/ri_cal.gemspec +49 -0
  93. data/sample_ical_files/from_ical_dot_app/test1.ics +38 -0
  94. data/script/console +10 -0
  95. data/script/destroy +14 -0
  96. data/script/generate +14 -0
  97. data/script/txt2html +71 -0
  98. data/spec/ri_cal/component/alarm_spec.rb +12 -0
  99. data/spec/ri_cal/component/calendar_spec.rb +54 -0
  100. data/spec/ri_cal/component/event_spec.rb +601 -0
  101. data/spec/ri_cal/component/freebusy_spec.rb +12 -0
  102. data/spec/ri_cal/component/journal_spec.rb +37 -0
  103. data/spec/ri_cal/component/t_z_info_timezone_spec.rb +36 -0
  104. data/spec/ri_cal/component/timezone_spec.rb +218 -0
  105. data/spec/ri_cal/component/todo_spec.rb +112 -0
  106. data/spec/ri_cal/component_spec.rb +224 -0
  107. data/spec/ri_cal/core_extensions/string/conversions_spec.rb +78 -0
  108. data/spec/ri_cal/core_extensions/time/calculations_spec.rb +188 -0
  109. data/spec/ri_cal/core_extensions/time/week_day_predicates_spec.rb +45 -0
  110. data/spec/ri_cal/occurrence_enumerator_spec.rb +573 -0
  111. data/spec/ri_cal/parser_spec.rb +303 -0
  112. data/spec/ri_cal/property_value/date_spec.rb +53 -0
  113. data/spec/ri_cal/property_value/date_time_spec.rb +383 -0
  114. data/spec/ri_cal/property_value/duration_spec.rb +126 -0
  115. data/spec/ri_cal/property_value/occurrence_list_spec.rb +72 -0
  116. data/spec/ri_cal/property_value/period_spec.rb +49 -0
  117. data/spec/ri_cal/property_value/recurrence_rule/recurring_year_day_spec.rb +21 -0
  118. data/spec/ri_cal/property_value/recurrence_rule_spec.rb +1814 -0
  119. data/spec/ri_cal/property_value/text_spec.rb +25 -0
  120. data/spec/ri_cal/property_value/utc_offset_spec.rb +48 -0
  121. data/spec/ri_cal/property_value_spec.rb +125 -0
  122. data/spec/ri_cal/required_timezones_spec.rb +67 -0
  123. data/spec/ri_cal_spec.rb +53 -0
  124. data/spec/spec.opts +4 -0
  125. data/spec/spec_helper.rb +46 -0
  126. data/tasks/gem_loader/load_active_support.rb +3 -0
  127. data/tasks/gem_loader/load_tzinfo_gem.rb +2 -0
  128. data/tasks/ri_cal.rake +410 -0
  129. data/tasks/spec.rake +50 -0
  130. metadata +221 -0
@@ -0,0 +1,25 @@
1
+ #- ©2009 Rick DeNatale, All rights reserved. Refer to the file README.txt for the license
2
+
3
+ require File.join(File.dirname(__FILE__), %w[.. .. spec_helper])
4
+
5
+ describe RiCal::PropertyValue::Text do
6
+
7
+ context ".ruby_value" do
8
+
9
+ it "should handle escapes according to RFC2445 Sec 4.3.11 p 45" do
10
+ expected = "this\\ has\, \nescaped\;\n\\x characters"
11
+ it = RiCal::PropertyValue::Text.new(nil, :value => 'this\\ has\, \nescaped\;\N\x characters')
12
+ it.ruby_value.should == expected
13
+ end
14
+ end
15
+
16
+ context ".convert" do
17
+
18
+ it "should handle escapes according to RFC2445 Sec 4.3.11 p 45" do
19
+ expected = ':this has\, \nescaped\;\n characters\ncr\nnlcr\ncrnl'
20
+ it = RiCal::PropertyValue::Text.convert(nil, "this\ has, \nescaped;\n characters\rcr\n\rnlcr\r\ncrnl")
21
+ it.to_s.should == expected
22
+ end
23
+ end
24
+
25
+ end
@@ -0,0 +1,48 @@
1
+ #- ©2009 Rick DeNatale, All rights reserved. Refer to the file README.txt for the license
2
+
3
+ require File.join(File.dirname(__FILE__), %w[.. .. spec_helper])
4
+
5
+ describe RiCal::PropertyValue::UtcOffset do
6
+
7
+ describe "with a positive sign and seconds" do
8
+ before(:each) do
9
+ @it = RiCal::PropertyValue::UtcOffset.new(nil, :value => "+013015")
10
+ end
11
+
12
+ it "should have +1 as its sign" do
13
+ @it.sign.should == 1
14
+ end
15
+
16
+ it "should have 1 as its hours" do
17
+ @it.hours.should == 1
18
+ end
19
+
20
+ it "should have 30 as its minutes" do
21
+ @it.minutes.should == 30
22
+ end
23
+
24
+ it "should have 15 as its seconds" do
25
+ @it.seconds.should == 15
26
+ end
27
+ end
28
+
29
+ describe "with seconds omitted" do
30
+ before(:each) do
31
+ @it = RiCal::PropertyValue::UtcOffset.new(nil, :value => "+0130")
32
+ end
33
+
34
+ it "should have 0 as its seconds" do
35
+ @it.seconds.should == 0
36
+ end
37
+ end
38
+ describe "with a negative sign" do
39
+ before(:each) do
40
+ @it = RiCal::PropertyValue::UtcOffset.new(nil, :value => "-013015")
41
+ end
42
+
43
+ it "should have +1 as its sign" do
44
+ @it.sign.should == -1
45
+ end
46
+
47
+ end
48
+ end
@@ -0,0 +1,125 @@
1
+ #- ©2009 Rick DeNatale, All rights reserved. Refer to the file README.txt for the license
2
+ require File.join(File.dirname(__FILE__), %w[.. spec_helper])
3
+ require 'tzinfo'
4
+
5
+ describe RiCal::PropertyValue do
6
+
7
+ describe ".initialize" do
8
+
9
+ it "should reject a value starting with ';'" do
10
+ lambda {RiCal::PropertyValue.new(nil, :value => ";bogus")}.should raise_error {|err| err.message.should == "Invalid property value \";bogus\""}
11
+ end
12
+ end
13
+
14
+ describe "#date_or_date_time" do
15
+
16
+ it "should raise an exception on an invalid date" do
17
+ lambda {RiCal::PropertyValue.date_or_date_time(nil, :value => "foo")}.should raise_error
18
+ end
19
+
20
+
21
+ describe "rfc 2445 section 4.3.4 p 34" do
22
+ before(:each) do
23
+ @prop = RiCal::PropertyValue.date_or_date_time(nil, :value => "19970714")
24
+ end
25
+
26
+ it "should return a PropertyValue::Date" do
27
+ @prop.should be_kind_of(RiCal::PropertyValue::Date)
28
+ end
29
+
30
+ it "should set the correct date" do
31
+ @prop.to_ri_cal_ruby_value.should == Date.parse("Jul 14, 1997")
32
+ end
33
+ end
34
+
35
+ describe "rfc 2445 section 4.3.5 p 35" do
36
+ describe "FORM #1 date with local time p 36" do
37
+ before(:each) do
38
+ @prop = RiCal::PropertyValue.date_or_date_time(nil, :value => "19970714T123456")
39
+ end
40
+
41
+ it "should return a PropertyValue::DateTime" do
42
+ @prop.should be_kind_of(RiCal::PropertyValue::DateTime)
43
+ end
44
+
45
+ it "should have the right ruby value" do
46
+ @prop.to_ri_cal_ruby_value.should == DateTime.parse("19970714T123456")
47
+ end
48
+
49
+ it "should have the right value" do
50
+ @prop.value.should == "19970714T123456"
51
+ end
52
+
53
+ it "should have a nil tzid" do
54
+ @prop.tzid.should == nil
55
+ end
56
+ end
57
+
58
+ describe "FORM #2 date with UTC time p 36" do
59
+ before(:each) do
60
+ @prop = RiCal::PropertyValue.date_or_date_time(nil, :value => "19970714T123456Z")
61
+ end
62
+
63
+ it "should return a PropertyValue::DateTime" do
64
+ @prop.should be_kind_of(RiCal::PropertyValue::DateTime)
65
+ end
66
+
67
+ it "should have the right value" do
68
+ @prop.value.should == "19970714T123456Z"
69
+ end
70
+
71
+ it "should have the right ruby value" do
72
+ @prop.to_ri_cal_ruby_value.should == DateTime.parse("19970714T123456Z")
73
+ end
74
+
75
+ it "should have a tzid of UTC" do
76
+ @prop.tzid.should == "UTC"
77
+ end
78
+
79
+ end
80
+
81
+ describe "FORM #3 date with local time and time zone reference p 36" do
82
+ before(:each) do
83
+ timezone = mock("Timezone",
84
+ :rational_utc_offset => Rational(-4, 24),
85
+ :local_to_utc => RiCal::PropertyValue.date_or_date_time(nil, :value => "19970714T163456Z"),
86
+ :name => 'US-Eastern'
87
+ )
88
+ timezone_finder = mock("tz_finder", :find_timezone => timezone)
89
+ @prop = RiCal::PropertyValue.date_or_date_time(timezone_finder, :value => "19970714T123456", :params => {'TZID' => 'US-Eastern'})
90
+ end
91
+
92
+ it "should return a PropertyValue::DateTime" do
93
+ @prop.should be_kind_of(RiCal::PropertyValue::DateTime)
94
+ end
95
+
96
+ it "should have the right value" do
97
+ @prop.value.should == "19970714T123456"
98
+ end
99
+
100
+ context "it's Ruby value" do
101
+ before(:each) do
102
+ @it = @prop.ruby_value
103
+ end
104
+
105
+ it "should be the right DateTime" do
106
+ @it.should == DateTime.civil(1997,7, 14, 12, 34, 56, Rational(-4, 24))
107
+ end
108
+
109
+ it "should have the right tzid" do
110
+ @it.tzid.should == "US-Eastern"
111
+ end
112
+ end
113
+
114
+ it "should have the right tzid" do
115
+ @prop.tzid.should == "US-Eastern"
116
+ end
117
+
118
+ it "should raise an error if combined with a zulu time" do
119
+ lambda {RiCal::PropertyValue.date_or_date_time(nil, :value => "19970714T123456Z", :params => {:tzid => 'US-Eastern'})}.should raise_error
120
+ end
121
+ end
122
+ end
123
+
124
+ end
125
+ end
@@ -0,0 +1,67 @@
1
+ #- ©2009 Rick DeNatale, All rights reserved. Refer to the file README.txt for the license
2
+
3
+ require File.join(File.dirname(__FILE__), %w[.. spec_helper])
4
+
5
+ require 'tzinfo'
6
+
7
+ describe RiCal::RequiredTimezones::RequiredTimezone do
8
+
9
+ before(:each) do
10
+ @tzid = "America/New_York"
11
+ @it = RiCal::RequiredTimezones::RequiredTimezone.new(@tzid)
12
+ end
13
+
14
+ it "should have the right timezone" do
15
+ @it.timezone.identifier.should == @tzid
16
+ end
17
+
18
+ it "should set the first_time to the first date_time added" do
19
+ dt1 = dt_prop(DateTime.parse("Mar 22, 2009 10:48"))
20
+ @it.add_datetime(dt1)
21
+ @it.first_time.should == dt1
22
+ end
23
+
24
+ it "should set the first_time to the earliest time added" do
25
+ dt1 = dt_prop(DateTime.parse("Mar 22, 2009 10:48"))
26
+ dt2 = dt_prop(DateTime.parse("Mar 22, 2009 9:00"))
27
+ dt3 = dt_prop(DateTime.parse("Mar 22, 2009 10:00"))
28
+ @it.add_datetime(dt1)
29
+ @it.add_datetime(dt2)
30
+ @it.add_datetime(dt3)
31
+ @it.first_time.should == dt2
32
+ end
33
+
34
+ it "should set the last_time to the first date_time added" do
35
+ dt1 = dt_prop(DateTime.parse("Mar 22, 2009 10:48"))
36
+ @it.add_datetime(dt1)
37
+ @it.last_time.should == dt1
38
+ end
39
+
40
+ it "should set the first_time to the earliest time added" do
41
+ dt1 = dt_prop(DateTime.parse("Mar 22, 2009 10:48"))
42
+ dt2 = dt_prop(DateTime.parse("Mar 22, 2009 9:00"))
43
+ dt3 = dt_prop(DateTime.parse("Mar 22, 2009 10:00"))
44
+ @it.add_datetime(dt1)
45
+ @it.add_datetime(dt2)
46
+ @it.add_datetime(dt3)
47
+ @it.last_time.should == dt1
48
+ end
49
+ end
50
+
51
+ describe RiCal::RequiredTimezones do
52
+ before(:each) do
53
+ @it = RiCal::RequiredTimezones.new
54
+ end
55
+
56
+ def localtime_and_zone(date_time, tzid = "US/Eastern")
57
+ [dt_prop(DateTime.parse(date_time), tzid), tzid]
58
+ end
59
+
60
+
61
+ it "should create a RequiredTimezone for each new timezone presented" do
62
+ @it.add_datetime(*localtime_and_zone("Mar 22, 2009 1:00"))
63
+ @it.add_datetime(*localtime_and_zone("Apr 16, 2008 12:00", "US/Central"))
64
+ @it.add_datetime(*localtime_and_zone("Apr 16, 2008 12:00"))
65
+ @it.required_zones.map {|zone| zone.tzid}.sort.should == ["US/Central", "US/Eastern"]
66
+ end
67
+ end
@@ -0,0 +1,53 @@
1
+ #- ©2009 Rick DeNatale, All rights reserved. Refer to the file README.txt for the license
2
+
3
+ require File.join(File.dirname(__FILE__), %w[spec_helper])
4
+
5
+ describe RiCal do
6
+
7
+
8
+ describe "#parse" do
9
+
10
+ before(:each) do
11
+ @mock_parser = mock("parser", :parse => [])
12
+ RiCal::Parser.stub!(:new).and_return(@mock_parser)
13
+ end
14
+
15
+ it "should create a parser using the io parameter" do
16
+ io = StringIO.new("")
17
+ RiCal::Parser.should_receive(:new).with(io).and_return(@mock_parser)
18
+ RiCal.parse(io)
19
+ end
20
+
21
+ it "should delegate to the parser" do
22
+ io = StringIO.new("")
23
+ @mock_parser.should_receive(:parse)
24
+ RiCal.parse(io)
25
+ end
26
+
27
+ it "should return the results of the parse" do
28
+ io = StringIO.new("")
29
+ @mock_parser.stub!(:parse).and_return(:parse_result)
30
+ RiCal.parse(io).should == :parse_result
31
+ end
32
+ end
33
+
34
+ describe "#parse_string" do
35
+ before(:each) do
36
+ @mock_io = :mock_io
37
+ StringIO.stub!(:new).and_return(@mock_io)
38
+ RiCal.stub!(:parse)
39
+ end
40
+
41
+ it "should create a StringIO from the string" do
42
+ string = "test string"
43
+ StringIO.should_receive(:new).with(string)
44
+ RiCal.parse_string(string)
45
+ end
46
+
47
+ it "should parse" do
48
+ RiCal.should_receive(:parse).with(@mock_io)
49
+ RiCal.parse_string("")
50
+ end
51
+ end
52
+
53
+ end
@@ -0,0 +1,4 @@
1
+ --colour
2
+ --format profile
3
+ --reverse
4
+ --backtrace
@@ -0,0 +1,46 @@
1
+ #- ©2009 Rick DeNatale, All rights reserved. Refer to the file README.txt for the license
2
+
3
+ require File.expand_path(File.join(File.dirname(__FILE__), %w[.. lib ri_cal]))
4
+ require 'cgi'
5
+ require 'tzinfo'
6
+
7
+ module Kernel
8
+ def rputs(*args)
9
+ puts *["<pre>", args.collect {|a| CGI.escapeHTML(a.to_s)}, "</pre>"] #if RiCal.debug
10
+ # puts *args
11
+ end
12
+ end
13
+
14
+ def date_time_with_zone(date_time, tzid = "US/Eastern")
15
+ date_time.dup.set_tzid(tzid)
16
+ end
17
+
18
+ def dt_prop(date_time, tzid = "US/Eastern")
19
+ RiCal::PropertyValue::DateTime.convert(nil, date_time_with_zone(date_time, tzid))
20
+ end
21
+
22
+ def offset_for_tzid(year, month, day, hour, min, sec, tzid, alternate)
23
+ tz = TZInfo::Timezone.get(tzid) rescue nil
24
+ if tz
25
+ Rational(tz.period_for_local(DateTime.civil(year, month, day, hour, min, sec)).utc_total_offset, 86400)
26
+ else
27
+ provided_offset
28
+ end
29
+ end
30
+
31
+ def rectify_ical(string)
32
+ string.gsub(/^\s+/, "")
33
+ end
34
+
35
+ if RiCal::TimeWithZone
36
+ def result_time_in_zone(year, month, day, hour, min, sec, tzid, alternate_offset = nil)
37
+ DateTime.civil(year, month, day, hour, min, sec,
38
+ offset_for_tzid(year, month, day, hour, min, sec, tzid, alternate_offset)).in_time_zone(tzid)
39
+ end
40
+ else
41
+ def result_time_in_zone(year, month, day, hour, min, sec, tzid, alternate_offset = nil)
42
+ DateTime.civil(year, month, day, hour, min, sec,
43
+ offset_for_tzid(year, month, day, hour, min, sec, tzid, alternate_offset)).set_tzid(tzid)
44
+ end
45
+ end
46
+
@@ -0,0 +1,3 @@
1
+ require 'rubygems'
2
+ gem 'activesupport', '<=2.2'
3
+ require 'active_support'
@@ -0,0 +1,2 @@
1
+ require 'rubygems'
2
+ require 'tzinfo'
@@ -0,0 +1,410 @@
1
+ #require 'active_support'
2
+ require 'yaml'
3
+ #- ©2009 Rick DeNatale, All rights reserved. Refer to the file README.txt for the license
4
+ #
5
+ # code stolen from ActiveSupport Gem
6
+ unless String.instance_methods.include?("camelize")
7
+ def camelize
8
+ self.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
9
+ end
10
+ end
11
+
12
+ class VEntityUpdater
13
+ Pluralization = {
14
+ "attach" => "attachments",
15
+ "categories" => "multiple_categories",
16
+ "free_busy" => "free_busies",
17
+ "security_class" => "security_classes",
18
+ "request_status" => "request_statuses",
19
+ "related_to" => "multiple_related_to",
20
+ "resources" => "multiple_resources"
21
+ }
22
+ def initialize(target, defs_file)
23
+ @target = target
24
+ @name=File.basename(target).sub(".rb","")
25
+ @indent = ""
26
+ @property_map = {}
27
+ @property_defs = YAML.load_file(defs_file)
28
+ @all_props = {}
29
+ @date_time_props = []
30
+ end
31
+
32
+ def property(prop_name_or_hash)
33
+ if Hash === prop_name_or_hash
34
+ name = prop_name_or_hash.keys[0]
35
+ override_options = prop_name_or_hash[name] || {}
36
+ else
37
+ name = prop_name_or_hash
38
+ override_options = {}
39
+ end
40
+ standard_options = @property_defs[name]
41
+ unless standard_options
42
+ puts "**** WARNING, no definition found for property #{name}"
43
+ standard_options = {}
44
+ end
45
+ options = {'type' => 'Text', 'ruby_name' => name}.merge(standard_options).merge(override_options)
46
+ named_property(name, options)
47
+ end
48
+
49
+ def indent(string)
50
+ @outfile.puts("#{@indent}#{string}")
51
+ end
52
+
53
+ def comment(*strings)
54
+ strings.each do |string|
55
+ indent("\# #{string}")
56
+ end
57
+ end
58
+
59
+ def no_doc(string)
60
+ indent("#{string} \# :nodoc:")
61
+ end
62
+
63
+ def blank_line
64
+ @outfile.puts
65
+ end
66
+
67
+ def describe_type(type)
68
+ case type
69
+ when 'date_time_or_date'
70
+ "either DateTime or Date"
71
+ when 'Text'
72
+ 'String'
73
+ else
74
+ type
75
+ end
76
+ end
77
+
78
+ def describe_property(type)
79
+ case type
80
+ when 'date_time_or_date'
81
+ "either RiCal::PropertyValue::DateTime or RiCal::PropertyValue::Date"
82
+ else
83
+ "RiCal::PropertyValue#{type}"
84
+ end
85
+ end
86
+
87
+ def type_class(type)
88
+ if type == 'date_time_or_date'
89
+ "RiCal::PropertyValue::DateTime"
90
+ else
91
+ "RiCal::PropertyValue::#{type}"
92
+ end
93
+ end
94
+
95
+ def cast_value(ruby_val, type)
96
+ "#{type_class(type)}.convert(self, #{ruby_val.inspect})"
97
+ end
98
+
99
+ def lazy_init_var(var, options)
100
+ const_val = options["constant_value"]
101
+ default_val = options["default_value"]
102
+ if options["multi"]
103
+ puts("*** Warning default_value of #{default_val} for multi-value property #{name} ignored") if default_val
104
+ puts("*** Warning const_value of #{const_val} for multi-value property #{name} ignored") if const_val
105
+ if var
106
+ "@#{var} ||= []"
107
+ else
108
+ "[]"
109
+ end
110
+ else
111
+ puts("*** Warning default_value of #{default_val} for property #{name} with constant_value of #{const_val}") if const_val && default_val
112
+ init_val = const_val || default_val
113
+ if init_val
114
+ if var
115
+ "@#{var} ||= #{cast_value(init_val, options["type"])}"
116
+ else
117
+ init_val.inspect
118
+ end
119
+ else
120
+ "@#{var}"
121
+ end
122
+ end
123
+ end
124
+
125
+ def pluralize(name)
126
+ Pluralization[name] || "#{name}s"
127
+ end
128
+
129
+ def named_property(name, options)
130
+ ruby_name = options['ruby_name']
131
+ multi = options['multi']
132
+ type = options['type']
133
+ rfc_ref = options['rfc_ref']
134
+ conflicts = options['conflicts_with']
135
+ type_constraint = options['type_constraint']
136
+ options.keys.each do |opt_key|
137
+ unless %w{
138
+ ruby_name
139
+ type
140
+ multi
141
+ rfc_ref
142
+ conflicts_with
143
+ purpose
144
+ constant_value
145
+ default_value
146
+ }.include?(opt_key)
147
+ puts "**** WARNING: unprocessed option key #{opt_key} for property #{name}"
148
+ end
149
+ end
150
+ if conflicts
151
+ mutually_exclusive(name, *conflicts)
152
+ end
153
+ needs_tz_access = %w{OccurrenceList date_time_or_date DateTime Date}.include?(type)
154
+ ruby_name = ruby_name.tr("-", "_")
155
+ ruby_method = ruby_name.downcase
156
+ property = "#{name.tr("-", "_").downcase}_property"
157
+ if type_constraint != 'must_be_utc' && %w[date_time_or_date DateTime Period OccurrenceList].include?(type)
158
+ @date_time_props << property
159
+ end
160
+ @all_props[property] = name.upcase
161
+ @property_map[name.upcase] = :"#{property}_from_string"
162
+ parent_set = needs_tz_access ? " ? property_value.for_parent(self) : nil" : ""
163
+ if type == 'date_time_or_date'
164
+ line_evaluator = "RiCal::PropertyValue::DateTime.or_date(self, line)"
165
+ else
166
+ line_evaluator = "#{type_class(type)}.new(self, line)"
167
+ end
168
+
169
+ if %w{Array, OccurrenceList}.include?(type)
170
+ ruby_val_parm = "*ruby_value"
171
+ val_parm = "*val"
172
+ else
173
+ ruby_val_parm = "ruby_value"
174
+ val_parm = "val"
175
+ end
176
+ blank_line
177
+ if multi
178
+ comment(
179
+ "return the the #{name.upcase} property",
180
+ "which will be an array of instances of #{describe_property(type)}"
181
+ )
182
+ comment("", "[purpose (from RFC 2445)]", options["purpose"]) if options["purpose"]
183
+ comment("", "see RFC 2445 #{rfc_ref}") if rfc_ref
184
+ indent("def #{property}")
185
+ indent(" #{lazy_init_var(property,options)}")
186
+ indent("end")
187
+ unless (options["constant_value"])
188
+ plural_ruby_method = pluralize(ruby_method)
189
+ blank_line
190
+ comment("set the the #{name.upcase} property")
191
+ comment("one or more instances of #{describe_property(type)} may be passed to this method")
192
+ indent("def #{property}=(*property_values)")
193
+ if needs_tz_access
194
+ indent(" @#{property}= property_values.map{|prop| prop.for_parent(self)}")
195
+ else
196
+ indent(" @#{property}= property_values")
197
+ end
198
+ indent("end")
199
+ blank_line
200
+ comment("set the value of the #{name.upcase} property to multiple values")
201
+ comment("one or more instances of #{describe_type(type)} may be passed to this method")
202
+ indent("def #{plural_ruby_method}=(ruby_values)")
203
+ indent(" @#{property} = ruby_values.map {|val| #{type_class(type)}.convert(self, #{val_parm})}")
204
+ indent("end")
205
+ blank_line
206
+ comment("set the value of the #{name.upcase} property to a single value")
207
+ comment("one instance of #{describe_type(type)} may be passed to this method")
208
+ indent("def #{ruby_method}=(#{ruby_val_parm})")
209
+ indent(" @#{property} = [#{type_class(type)}.convert(self, #{ruby_val_parm})]")
210
+ indent("end")
211
+ blank_line
212
+ comment("add one or more values to the #{name.upcase} property")
213
+ comment("one or more instances of #{describe_type(type)} may be passed to this method")
214
+ indent("def add_#{plural_ruby_method}(*ruby_values)")
215
+ indent(" ruby_values.each {|val| self.#{property} << #{type_class(type)}.convert(self, #{val_parm})}")
216
+ indent("end")
217
+ blank_line
218
+ comment("add one value to the #{name.upcase} property")
219
+ comment("one instances of #{describe_type(type)} may be passed to this method")
220
+ indent("def add_#{ruby_method}(#{ruby_val_parm})")
221
+ indent(" self.#{property} << #{type_class(type)}.convert(self, #{ruby_val_parm})")
222
+ indent("end")
223
+ blank_line
224
+ comment("remove one or more values from the #{name.upcase} property")
225
+ comment("one or more instances of #{describe_type(type)} may be passed to this method")
226
+ indent("def remove_#{plural_ruby_method}(*ruby_values)")
227
+ indent(" ruby_values.each {|val| self.#{property}.delete(#{type_class(type)}.convert(self, #{val_parm}))}")
228
+ indent("end")
229
+ blank_line
230
+ comment("remove one value from the #{name.upcase} property")
231
+ comment("one instances of #{describe_type(type)} may be passed to this method")
232
+ indent("def remove_#{ruby_method}(#{ruby_val_parm})")
233
+ indent(" self.#{property}.delete(#{type_class(type)}.convert(self, #{ruby_val_parm}))")
234
+ indent("end")
235
+ end
236
+ blank_line
237
+ comment("return the value of the #{name.upcase} property")
238
+ comment("which will be an array of instances of #{describe_type(type)}")
239
+ indent("def #{ruby_method}")
240
+ indent(" #{property}.map {|prop| prop ? prop.ruby_value : prop}")
241
+ indent("end")
242
+ blank_line
243
+ no_doc("def #{property}_from_string(line)")
244
+ indent(" #{property} << #{line_evaluator}")
245
+ indent("end")
246
+ else
247
+ comment(
248
+ "return the the #{name.upcase} property",
249
+ "which will be an instances of #{describe_property(type)}"
250
+ )
251
+ comment("", "[purpose (from RFC 2445)]", options["purpose"]) if options["purpose"]
252
+ comment("", "see RFC 2445 #{rfc_ref}") if rfc_ref
253
+ indent("def #{property}")
254
+ indent(" #{lazy_init_var(property,options)}")
255
+ indent("end")
256
+ unless (options["constant_value"])
257
+ blank_line
258
+ comment("set the #{name.upcase} property")
259
+ comment("property value should be an instance of #{describe_property(type)}")
260
+ indent("def #{property}=(property_value)")
261
+ indent(" @#{property} = property_value#{parent_set}")
262
+ if conflicts
263
+ conflicts.each do |conflict|
264
+ indent(" @#{conflict}_property = nil")
265
+ end
266
+ end
267
+ indent("end")
268
+ blank_line
269
+ comment("set the value of the #{name.upcase} property")
270
+ indent("def #{ruby_method}=(ruby_value)")
271
+ indent(" self.#{property}= #{type_class(type)}.convert(self, ruby_value)")
272
+ indent("end")
273
+ end
274
+ blank_line
275
+ comment("return the value of the #{name.upcase} property")
276
+ comment("which will be an instance of #{describe_type(type)}")
277
+ indent("def #{ruby_method}")
278
+ indent(" #{property} ? #{property}.ruby_value : nil")
279
+ indent("end")
280
+ blank_line
281
+ no_doc("def #{property}_from_string(line)")
282
+ indent(" @#{property} = #{line_evaluator}")
283
+ indent("end")
284
+ @outfile.puts
285
+ end
286
+ end
287
+
288
+ def mutually_exclusive *prop_names
289
+ exclusives = prop_names.map {|str| :"#{str}_property"}.sort {|a, b| a.to_s <=> b.to_s}
290
+ unless mutually_exclusive_properties.include?(exclusives)
291
+ mutually_exclusive_properties << prop_names.map {|str| :"#{str}_property"}
292
+ end
293
+ end
294
+
295
+ def mutually_exclusive_properties
296
+ @mutually_exclusive_properties ||= []
297
+ end
298
+
299
+ def generate_support_methods
300
+ blank_line
301
+ indent("def export_properties_to(export_stream) #:nodoc:")
302
+ @all_props.each do |prop_attr, prop_name|
303
+ indent(" export_prop_to(export_stream, #{prop_name.inspect}, @#{prop_attr})")
304
+ end
305
+ indent("end")
306
+ blank_line
307
+ indent("def ==(o) #:nodoc:")
308
+ indent(" if o.class == self.class")
309
+ @all_props.keys.each_with_index do |prop_name, i|
310
+ and_str = i < @all_props.length - 1 ? " &&" : ""
311
+ indent(" (#{prop_name} == o.#{prop_name})#{and_str}")
312
+ end
313
+ indent(" else")
314
+ indent(" super")
315
+ indent(" end")
316
+ indent("end")
317
+ blank_line
318
+ indent("def initialize_copy(o) #:nodoc:")
319
+ indent(" super")
320
+ @all_props.each_key do |prop_name|
321
+ indent(" #{prop_name} = #{prop_name} && #{prop_name}.dup")
322
+ end
323
+ indent("end")
324
+ blank_line
325
+ indent("def add_date_times_to(required_timezones) #:nodoc:")
326
+ @date_time_props.each do | prop_name|
327
+ indent(" add_property_date_times_to(required_timezones, #{prop_name})")
328
+ end
329
+ indent("end")
330
+ blank_line
331
+ indent("module ClassMethods #:nodoc:")
332
+ indent(" def property_parser #:nodoc:")
333
+ indent(" #{@property_map.inspect}")
334
+ indent(" end")
335
+ indent("end")
336
+ blank_line
337
+ indent("def self.included(mod) #:nodoc:")
338
+ indent(" mod.extend ClassMethods")
339
+ indent("end")
340
+ blank_line
341
+ indent("def mutual_exclusion_violation #:nodoc:")
342
+ if mutually_exclusive_properties.empty?
343
+ indent(" false")
344
+ else
345
+ mutually_exclusive_properties.each do |mutex_set|
346
+ indent(" return true if #{mutex_set.inspect}.inject(0) {|sum, prop| send(prop) ? sum + 1 : sum} > 1")
347
+ end
348
+ indent(" false")
349
+ end
350
+ indent "end"
351
+ end
352
+
353
+ def update
354
+ File.open(File.join(File.dirname(__FILE__), '..', 'lib', 'ri_cal', 'properties' , "#{@name}.rb"), 'w') do |ruby_out_file|
355
+ @outfile = ruby_out_file
356
+ module_name = @name.camelize
357
+ class_name = module_name.sub(/Properties$/, "")
358
+ ruby_out_file.puts("module RiCal")
359
+ ruby_out_file.puts(" module Properties #:nodoc:")
360
+ @indent = " "
361
+ ruby_out_file.puts(" #- ©2009 Rick DeNatale")
362
+ ruby_out_file.puts(" #- All rights reserved. Refer to the file README.txt for the license")
363
+ ruby_out_file.puts(" #")
364
+ ruby_out_file.puts(" # Properties::#{module_name} provides property accessing methods for the #{class_name} class")
365
+ ruby_out_file.puts(" # This source file is generated by the rical:gen_propmodules rake tasks, DO NOT EDIT")
366
+ ruby_out_file.puts(" module #{module_name}")
367
+ @indent = " "
368
+ YAML.load_file(File.join(File.dirname(__FILE__), '..', 'component_attributes', "#{@name}.yml")).each do |att_def|
369
+ property(att_def)
370
+ end
371
+ generate_support_methods
372
+ ruby_out_file.puts(" end")
373
+ ruby_out_file.puts(" end")
374
+ ruby_out_file.puts("end")
375
+ end
376
+ @outfile = nil
377
+ end
378
+ end
379
+
380
+ def updateTask srcGlob, taskSymbol
381
+ targetDir = File.join(File.dirname(__FILE__), '..', 'lib', 'ri_cal', 'properties')
382
+ defsFile = File.join(File.dirname(__FILE__), '..', 'component_attributes', 'component_property_defs.yml')
383
+ FileList[srcGlob].each do |f|
384
+ unless f == defsFile
385
+ target = File.join targetDir, File.basename(f).gsub(".yml", ".rb")
386
+ file target => [f, defsFile, __FILE__] do |t|
387
+ VEntityUpdater.new(target, defsFile).update
388
+ end
389
+ task taskSymbol => target
390
+ end
391
+ end
392
+ end
393
+
394
+
395
+ namespace :rical do
396
+
397
+ desc '(RE)Generate VEntity attributes'
398
+ task :gen_propmodules do |t|
399
+ end
400
+
401
+ updateTask File.join(File.dirname(__FILE__), '..', '/component_attributes', '*.yml'), :gen_propmodules
402
+
403
+ end # namespace :rical
404
+
405
+ desc 'add or update copyright in code and specs'
406
+ task :copyrights do
407
+ require 'mmcopyrights'
408
+ MM::Copyrights.process('lib', "rb", "#-", IO.read('copyrights.txt'))
409
+ MM::Copyrights.process('spec', "rb", "#-", IO.read('copyrights.txt'))
410
+ end