ri_cal 0.5.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.
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