ri_cal 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +45 -0
- data/Manifest.txt +129 -0
- data/README.txt +394 -0
- data/Rakefile +31 -0
- data/bin/ri_cal +8 -0
- data/component_attributes/alarm.yml +10 -0
- data/component_attributes/calendar.yml +4 -0
- data/component_attributes/component_property_defs.yml +180 -0
- data/component_attributes/event.yml +45 -0
- data/component_attributes/freebusy.yml +16 -0
- data/component_attributes/journal.yml +35 -0
- data/component_attributes/timezone.yml +3 -0
- data/component_attributes/timezone_period.yml +11 -0
- data/component_attributes/todo.yml +46 -0
- data/copyrights.txt +1 -0
- data/docs/draft-ietf-calsify-2446bis-08.txt +7280 -0
- data/docs/draft-ietf-calsify-rfc2445bis-09.txt +10416 -0
- data/docs/incrementers.txt +7 -0
- data/docs/rfc2445.pdf +0 -0
- data/lib/ri_cal.rb +144 -0
- data/lib/ri_cal/component.rb +247 -0
- data/lib/ri_cal/component/alarm.rb +21 -0
- data/lib/ri_cal/component/calendar.rb +219 -0
- data/lib/ri_cal/component/event.rb +60 -0
- data/lib/ri_cal/component/freebusy.rb +18 -0
- data/lib/ri_cal/component/journal.rb +30 -0
- data/lib/ri_cal/component/t_z_info_timezone.rb +123 -0
- data/lib/ri_cal/component/timezone.rb +196 -0
- data/lib/ri_cal/component/timezone/daylight_period.rb +25 -0
- data/lib/ri_cal/component/timezone/standard_period.rb +23 -0
- data/lib/ri_cal/component/timezone/timezone_period.rb +53 -0
- data/lib/ri_cal/component/todo.rb +43 -0
- data/lib/ri_cal/core_extensions.rb +6 -0
- data/lib/ri_cal/core_extensions/array.rb +7 -0
- data/lib/ri_cal/core_extensions/array/conversions.rb +15 -0
- data/lib/ri_cal/core_extensions/date.rb +13 -0
- data/lib/ri_cal/core_extensions/date/conversions.rb +61 -0
- data/lib/ri_cal/core_extensions/date_time.rb +15 -0
- data/lib/ri_cal/core_extensions/date_time/conversions.rb +50 -0
- data/lib/ri_cal/core_extensions/object.rb +8 -0
- data/lib/ri_cal/core_extensions/object/conversions.rb +20 -0
- data/lib/ri_cal/core_extensions/string.rb +8 -0
- data/lib/ri_cal/core_extensions/string/conversions.rb +63 -0
- data/lib/ri_cal/core_extensions/time.rb +13 -0
- data/lib/ri_cal/core_extensions/time/calculations.rb +153 -0
- data/lib/ri_cal/core_extensions/time/conversions.rb +61 -0
- data/lib/ri_cal/core_extensions/time/tzid_access.rb +50 -0
- data/lib/ri_cal/core_extensions/time/week_day_predicates.rb +88 -0
- data/lib/ri_cal/floating_timezone.rb +32 -0
- data/lib/ri_cal/invalid_property_value.rb +8 -0
- data/lib/ri_cal/invalid_timezone_identifer.rb +20 -0
- data/lib/ri_cal/occurrence_enumerator.rb +206 -0
- data/lib/ri_cal/occurrence_period.rb +17 -0
- data/lib/ri_cal/parser.rb +138 -0
- data/lib/ri_cal/properties/alarm.rb +390 -0
- data/lib/ri_cal/properties/calendar.rb +164 -0
- data/lib/ri_cal/properties/event.rb +1526 -0
- data/lib/ri_cal/properties/freebusy.rb +594 -0
- data/lib/ri_cal/properties/journal.rb +1240 -0
- data/lib/ri_cal/properties/timezone.rb +151 -0
- data/lib/ri_cal/properties/timezone_period.rb +416 -0
- data/lib/ri_cal/properties/todo.rb +1562 -0
- data/lib/ri_cal/property_value.rb +149 -0
- data/lib/ri_cal/property_value/array.rb +27 -0
- data/lib/ri_cal/property_value/cal_address.rb +11 -0
- data/lib/ri_cal/property_value/date.rb +175 -0
- data/lib/ri_cal/property_value/date_time.rb +335 -0
- data/lib/ri_cal/property_value/date_time/additive_methods.rb +44 -0
- data/lib/ri_cal/property_value/date_time/time_machine.rb +181 -0
- data/lib/ri_cal/property_value/date_time/timezone_support.rb +96 -0
- data/lib/ri_cal/property_value/duration.rb +110 -0
- data/lib/ri_cal/property_value/geo.rb +11 -0
- data/lib/ri_cal/property_value/integer.rb +12 -0
- data/lib/ri_cal/property_value/occurrence_list.rb +144 -0
- data/lib/ri_cal/property_value/period.rb +82 -0
- data/lib/ri_cal/property_value/recurrence_rule.rb +145 -0
- data/lib/ri_cal/property_value/recurrence_rule/enumeration_support_methods.rb +97 -0
- data/lib/ri_cal/property_value/recurrence_rule/enumerator.rb +79 -0
- data/lib/ri_cal/property_value/recurrence_rule/initialization_methods.rb +148 -0
- data/lib/ri_cal/property_value/recurrence_rule/negative_setpos_enumerator.rb +53 -0
- data/lib/ri_cal/property_value/recurrence_rule/numbered_span.rb +31 -0
- data/lib/ri_cal/property_value/recurrence_rule/occurence_incrementer.rb +793 -0
- data/lib/ri_cal/property_value/recurrence_rule/recurring_day.rb +131 -0
- data/lib/ri_cal/property_value/recurrence_rule/recurring_month_day.rb +60 -0
- data/lib/ri_cal/property_value/recurrence_rule/recurring_numbered_week.rb +33 -0
- data/lib/ri_cal/property_value/recurrence_rule/recurring_year_day.rb +49 -0
- data/lib/ri_cal/property_value/recurrence_rule/validations.rb +125 -0
- data/lib/ri_cal/property_value/text.rb +40 -0
- data/lib/ri_cal/property_value/uri.rb +11 -0
- data/lib/ri_cal/property_value/utc_offset.rb +33 -0
- data/lib/ri_cal/required_timezones.rb +55 -0
- data/ri_cal.gemspec +49 -0
- data/sample_ical_files/from_ical_dot_app/test1.ics +38 -0
- data/script/console +10 -0
- data/script/destroy +14 -0
- data/script/generate +14 -0
- data/script/txt2html +71 -0
- data/spec/ri_cal/component/alarm_spec.rb +12 -0
- data/spec/ri_cal/component/calendar_spec.rb +54 -0
- data/spec/ri_cal/component/event_spec.rb +601 -0
- data/spec/ri_cal/component/freebusy_spec.rb +12 -0
- data/spec/ri_cal/component/journal_spec.rb +37 -0
- data/spec/ri_cal/component/t_z_info_timezone_spec.rb +36 -0
- data/spec/ri_cal/component/timezone_spec.rb +218 -0
- data/spec/ri_cal/component/todo_spec.rb +112 -0
- data/spec/ri_cal/component_spec.rb +224 -0
- data/spec/ri_cal/core_extensions/string/conversions_spec.rb +78 -0
- data/spec/ri_cal/core_extensions/time/calculations_spec.rb +188 -0
- data/spec/ri_cal/core_extensions/time/week_day_predicates_spec.rb +45 -0
- data/spec/ri_cal/occurrence_enumerator_spec.rb +573 -0
- data/spec/ri_cal/parser_spec.rb +303 -0
- data/spec/ri_cal/property_value/date_spec.rb +53 -0
- data/spec/ri_cal/property_value/date_time_spec.rb +383 -0
- data/spec/ri_cal/property_value/duration_spec.rb +126 -0
- data/spec/ri_cal/property_value/occurrence_list_spec.rb +72 -0
- data/spec/ri_cal/property_value/period_spec.rb +49 -0
- data/spec/ri_cal/property_value/recurrence_rule/recurring_year_day_spec.rb +21 -0
- data/spec/ri_cal/property_value/recurrence_rule_spec.rb +1814 -0
- data/spec/ri_cal/property_value/text_spec.rb +25 -0
- data/spec/ri_cal/property_value/utc_offset_spec.rb +48 -0
- data/spec/ri_cal/property_value_spec.rb +125 -0
- data/spec/ri_cal/required_timezones_spec.rb +67 -0
- data/spec/ri_cal_spec.rb +53 -0
- data/spec/spec.opts +4 -0
- data/spec/spec_helper.rb +46 -0
- data/tasks/gem_loader/load_active_support.rb +3 -0
- data/tasks/gem_loader/load_tzinfo_gem.rb +2 -0
- data/tasks/ri_cal.rake +410 -0
- data/tasks/spec.rake +50 -0
- metadata +221 -0
data/docs/rfc2445.pdf
ADDED
Binary file
|
data/lib/ri_cal.rb
ADDED
@@ -0,0 +1,144 @@
|
|
1
|
+
#- ©2009 Rick DeNatale, All rights reserved. Refer to the file README.txt for the license
|
2
|
+
#
|
3
|
+
# The RiCal module provides the outermost namespace, along with several convenience methods for parsing
|
4
|
+
# and building calendars and calendar components.
|
5
|
+
module RiCal
|
6
|
+
|
7
|
+
my_dir = File.dirname(__FILE__)
|
8
|
+
|
9
|
+
autoload :Component, "#{my_dir}/ri_cal/component.rb"
|
10
|
+
autoload :TimezonePeriod, "#{my_dir}/ri_cal/properties/timezone_period.rb"
|
11
|
+
autoload :OccurrenceEnumerator, "#{my_dir}/ri_cal/occurrence_enumerator.rb"
|
12
|
+
|
13
|
+
# :stopdoc:
|
14
|
+
VERSION = '0.5.0'
|
15
|
+
LIBPATH = ::File.expand_path(::File.dirname(__FILE__)) + ::File::SEPARATOR
|
16
|
+
PATH = ::File.dirname(LIBPATH) + ::File::SEPARATOR
|
17
|
+
|
18
|
+
# Returns the version string for the library.
|
19
|
+
#
|
20
|
+
def self.version
|
21
|
+
VERSION
|
22
|
+
end
|
23
|
+
|
24
|
+
# Returns the library path for the module. If any arguments are given,
|
25
|
+
# they will be joined to the end of the libray path using
|
26
|
+
# <tt>File.join</tt>.
|
27
|
+
#
|
28
|
+
def self.libpath( *args )
|
29
|
+
args.empty? ? LIBPATH : ::File.join(LIBPATH, args.flatten)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Returns the lpath for the module. If any arguments are given,
|
33
|
+
# they will be joined to the end of the path using
|
34
|
+
# <tt>File.join</tt>.
|
35
|
+
#
|
36
|
+
def self.path( *args )
|
37
|
+
args.empty? ? PATH : ::File.join(PATH, args.flatten)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Utility method used to rquire all files ending in .rb that lie in the
|
41
|
+
# directory below this file that has the same name as the filename passed
|
42
|
+
# in. Optionally, a specific _directory_ name can be passed in such that
|
43
|
+
# the _filename_ does not have to be equivalent to the directory.
|
44
|
+
#
|
45
|
+
def self.require_all_libs_relative_to( fname, dir = nil )
|
46
|
+
dir ||= ::File.basename(fname, '.*')
|
47
|
+
search_me = ::File.expand_path(::File.join(::File.dirname(fname), dir, '**', '*.rb'))
|
48
|
+
Dir.glob(search_me).sort.each {|rb|
|
49
|
+
require rb}
|
50
|
+
end
|
51
|
+
|
52
|
+
# :startdoc:
|
53
|
+
|
54
|
+
# Parse an io stream and return an array of iCalendar entities.
|
55
|
+
# Normally this will be an array of RiCal::Component::Calendar instances
|
56
|
+
def self.parse(io)
|
57
|
+
Parser.new(io).parse
|
58
|
+
end
|
59
|
+
|
60
|
+
# Parse a string and return an array of iCalendar entities.
|
61
|
+
# see RiCal.parse
|
62
|
+
def self.parse_string(string)
|
63
|
+
parse(StringIO.new(string))
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.debug # :nodoc:
|
67
|
+
@debug
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.debug=(val) # :nodoc:
|
71
|
+
@debug = val
|
72
|
+
end
|
73
|
+
|
74
|
+
# return a new Alarm event or todo component. If a block is provided it will will be executed in
|
75
|
+
# the context of a builder object which can be used to initialize the properties of the
|
76
|
+
# new Alarm.
|
77
|
+
def self.Alarm(&init_block)
|
78
|
+
Component::Alarm.new(&init_block)
|
79
|
+
end
|
80
|
+
|
81
|
+
# return a new Calendar. If a block is provided it will will be executed in
|
82
|
+
# the context of a builder object which can be used to initialize the properties and components of the
|
83
|
+
# new calendar.
|
84
|
+
def self.Calendar(&init_block)
|
85
|
+
Component::Calendar.new(&init_block)
|
86
|
+
end
|
87
|
+
|
88
|
+
# return a new Event calendar component. If a block is provided it will will be executed in
|
89
|
+
# the context of a builder object which can be used to initialize the properties and alarms of the
|
90
|
+
# new Event.
|
91
|
+
def self.Event(&init_block)
|
92
|
+
Component::Event.new(&init_block)
|
93
|
+
end
|
94
|
+
|
95
|
+
# return a new Freebusy calendar component. If a block is provided it will will be executed in
|
96
|
+
# the context of a builder object which can be used to initialize the properties and components of the
|
97
|
+
# new Freebusy.
|
98
|
+
def self.Freebusy(&init_block)
|
99
|
+
Component::Freebusy.new(&init_block)
|
100
|
+
end
|
101
|
+
|
102
|
+
# return a new Journal calendar component. If a block is provided it will will be executed in
|
103
|
+
# the context of a builder object which can be used to initialize the properties and components of the
|
104
|
+
# new Event.
|
105
|
+
def self.Journal(&init_block)
|
106
|
+
Component::Journal.new(&init_block)
|
107
|
+
end
|
108
|
+
|
109
|
+
# return a new Timezone calendar component. If a block is provided it will will be executed in
|
110
|
+
# the context of a builder object which can be used to initialize the properties and timezone periods of the
|
111
|
+
# new Timezone.
|
112
|
+
def self.Timezone(&init_block)
|
113
|
+
Component::Timezone.new(&init_block)
|
114
|
+
end
|
115
|
+
|
116
|
+
# return a new TimezonePeriod timezone component. If a block is provided it will will be executed in
|
117
|
+
# the context of a builder object which can be used to initialize the properties of the
|
118
|
+
# new TimezonePeriod.
|
119
|
+
def self.TimezonePeriod(&init_block)
|
120
|
+
Component::TimezonePeriod.new(&init_block)
|
121
|
+
end
|
122
|
+
|
123
|
+
if Object.const_defined?(:ActiveSupport)
|
124
|
+
as = Object.const_get(:ActiveSupport)
|
125
|
+
if as.const_defined?(:TimeWithZone)
|
126
|
+
time_with_zone = as.const_get(:TimeWithZone)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
# TimeWithZone will be set to ActiveSupport::TimeWithZone if the activesupport gem is loaded
|
131
|
+
# otherwise it will be nil
|
132
|
+
TimeWithZone = time_with_zone
|
133
|
+
|
134
|
+
# return a new Todo calendar component. If a block is provided it will will be executed in
|
135
|
+
# the context of a builder object which can be used to initialize the properties and alarms of the
|
136
|
+
# new Todo.
|
137
|
+
def self.Todo(&init_block)
|
138
|
+
Component::Todo.new(&init_block)
|
139
|
+
end
|
140
|
+
end # module RiCal
|
141
|
+
|
142
|
+
RiCal.require_all_libs_relative_to(__FILE__)
|
143
|
+
|
144
|
+
# EOF
|
@@ -0,0 +1,247 @@
|
|
1
|
+
module RiCal
|
2
|
+
#- ©2009 Rick DeNatale, All rights reserved. Refer to the file README.txt for the license
|
3
|
+
#
|
4
|
+
class Component #:nodoc:
|
5
|
+
class ComponentBuilder #:nodoc:
|
6
|
+
def initialize(component)
|
7
|
+
@component = component
|
8
|
+
end
|
9
|
+
|
10
|
+
def method_missing(selector, *args, &init_block) #:nodoc:
|
11
|
+
if(sub_comp_class = @component.subcomponent_class[selector])
|
12
|
+
if init_block
|
13
|
+
sub_comp = sub_comp_class.new(@component)
|
14
|
+
if init_block.arity == 1
|
15
|
+
yield ComponentBuilder.new(sub_comp)
|
16
|
+
else
|
17
|
+
ComponentBuilder.new(sub_comp).instance_eval(&init_block)
|
18
|
+
end
|
19
|
+
self.add_subcomponent(sub_comp)
|
20
|
+
end
|
21
|
+
else
|
22
|
+
sel = selector.to_s
|
23
|
+
sel = "#{sel}=" unless /(^(add_)|(remove_))|(=$)/ =~ sel
|
24
|
+
if @component.respond_to?(sel)
|
25
|
+
@component.send(sel, *args)
|
26
|
+
else
|
27
|
+
super
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
autoload :Timezone, "#{File.dirname(__FILE__)}/component/timezone.rb"
|
34
|
+
|
35
|
+
attr_accessor :imported #:nodoc:
|
36
|
+
|
37
|
+
def initialize(parent=nil, &init_block) #:nodoc:
|
38
|
+
@parent = parent
|
39
|
+
if block_given?
|
40
|
+
if init_block.arity == 1
|
41
|
+
init_block.call(ComponentBuilder.new(self))
|
42
|
+
else
|
43
|
+
ComponentBuilder.new(self).instance_eval(&init_block)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def default_tzid #:nodoc:
|
49
|
+
if @parent
|
50
|
+
@parent.default_tzid
|
51
|
+
else
|
52
|
+
PropertyValue::DateTime.default_tzid
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def find_timezone(identifier) #:nodoc:
|
57
|
+
if @parent
|
58
|
+
@parent.find_timezone(identifier)
|
59
|
+
else
|
60
|
+
begin
|
61
|
+
Calendar::TZInfoWrapper.new(TZInfo::Timezone.get(identifier), self)
|
62
|
+
rescue ::TZInfo::InvalidTimezoneIdentifier => ex
|
63
|
+
raise RiCal::InvalidTimezoneIdentifier.invalid_tzinfo_identifier(identifier)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def tz_info_source?
|
69
|
+
if @parent
|
70
|
+
@parent.tz_info_source?
|
71
|
+
else
|
72
|
+
true
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def time_zone_for(ruby_object) #:nodoc:
|
77
|
+
@parent.time_zone_for(ruby_object) #:nodoc:
|
78
|
+
end
|
79
|
+
|
80
|
+
def subcomponent_class #:nodoc:
|
81
|
+
{}
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.from_parser(parser, parent) #:nodoc:
|
85
|
+
entity = self.new(parent)
|
86
|
+
entity.imported = true
|
87
|
+
line = parser.next_separated_line
|
88
|
+
while parser.still_in(entity_name, line)
|
89
|
+
entity.process_line(parser, line)
|
90
|
+
line = parser.next_separated_line
|
91
|
+
end
|
92
|
+
entity
|
93
|
+
end
|
94
|
+
|
95
|
+
def self.parse(io) #:nodoc:
|
96
|
+
Parser.new(io).parse
|
97
|
+
end
|
98
|
+
|
99
|
+
def imported? #:nodoc:
|
100
|
+
imported
|
101
|
+
end
|
102
|
+
|
103
|
+
def self.parse_string(string) #:nodoc:
|
104
|
+
parse(StringIO.new(string))
|
105
|
+
end
|
106
|
+
|
107
|
+
def subcomponents #:nodoc:
|
108
|
+
@subcomponents ||= Hash.new {|h, k| h[k] = []}
|
109
|
+
end
|
110
|
+
|
111
|
+
def entity_name #:nodoc:
|
112
|
+
self.class.entity_name
|
113
|
+
end
|
114
|
+
|
115
|
+
# return an array of Alarm components within this component :nodoc:
|
116
|
+
# Alarms may be contained within Events, and Todos
|
117
|
+
def alarms
|
118
|
+
subcomponents["VALARM"]
|
119
|
+
end
|
120
|
+
|
121
|
+
def add_subcomponent(component) #:nodoc:
|
122
|
+
subcomponents[component.entity_name] << component
|
123
|
+
end
|
124
|
+
|
125
|
+
def parse_subcomponent(parser, line) #:nodoc:
|
126
|
+
subcomponents[line[:value]] << parser.parse_one(line, self)
|
127
|
+
end
|
128
|
+
|
129
|
+
def process_line(parser, line) #:nodoc:
|
130
|
+
if line[:name] == "BEGIN"
|
131
|
+
parse_subcomponent(parser, line)
|
132
|
+
else
|
133
|
+
setter = self.class.property_parser[line[:name]]
|
134
|
+
if setter
|
135
|
+
send(setter, line)
|
136
|
+
else
|
137
|
+
self.add_x_property(line[:name], PropertyValue::Text.new(self, line))
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
# return a hash of any extended properties, (i.e. those with a property name starting with "X-"
|
143
|
+
# representing an extension to the RFC 2445 specification)
|
144
|
+
def x_properties
|
145
|
+
@x_properties ||= {}
|
146
|
+
end
|
147
|
+
|
148
|
+
# Add a n extended property
|
149
|
+
def add_x_property(name, prop)
|
150
|
+
x_properties[name] = prop
|
151
|
+
end
|
152
|
+
|
153
|
+
def method_missing(selector, *args, &b) #:nodoc:
|
154
|
+
xprop_candidate = selector.to_s
|
155
|
+
if (match = /^x_(.+)(=?)$/.match(xprop_candidate))
|
156
|
+
if match[2] == "="
|
157
|
+
add_x_property("x_#{match[1]}", *args)
|
158
|
+
else
|
159
|
+
x_properties[xprop_candidate]
|
160
|
+
end
|
161
|
+
else
|
162
|
+
super
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
# Predicate to determine if the component is valid according to RFC 2445
|
167
|
+
def valid?
|
168
|
+
!mutual_exclusion_violation
|
169
|
+
end
|
170
|
+
|
171
|
+
def initialize_copy(original) #:nodoc:
|
172
|
+
end
|
173
|
+
|
174
|
+
def prop_string(prop_name, *properties) #:nodoc:
|
175
|
+
properties = properties.flatten.compact
|
176
|
+
if properties && !properties.empty?
|
177
|
+
properties.map {|prop| "#{prop_name}#{prop.to_s}"}.join("\n")
|
178
|
+
else
|
179
|
+
nil
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
def add_property_date_times_to(required_timezones, property) #:nodoc:
|
184
|
+
if property
|
185
|
+
if Array === property
|
186
|
+
property.each do |prop|
|
187
|
+
prop.add_date_times_to(required_timezones)
|
188
|
+
end
|
189
|
+
else
|
190
|
+
property.add_date_times_to(required_timezones)
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
def export_prop_to(export_stream, name, prop) #:nodoc:
|
196
|
+
if prop
|
197
|
+
string = prop_string(name, prop)
|
198
|
+
export_stream.puts(string) if string
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
def export_x_properties_to(export_stream) #:nodoc:
|
203
|
+
x_properties.each do |name, prop|
|
204
|
+
export_stream.puts("#{name}:#{prop}")
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
def export_subcomponent_to(export_stream, subcomponent) #:nodoc:
|
209
|
+
subcomponent.each do |component|
|
210
|
+
component.export_to(export_stream)
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
# return a string containing the rfc2445 format of the component
|
215
|
+
def to_s
|
216
|
+
io = StringIO.new
|
217
|
+
export_to(io)
|
218
|
+
io.string
|
219
|
+
end
|
220
|
+
|
221
|
+
# Export this component to an export stream
|
222
|
+
def export_to(export_stream)
|
223
|
+
export_stream.puts("BEGIN:#{entity_name}")
|
224
|
+
export_properties_to(export_stream)
|
225
|
+
export_x_properties_to(export_stream)
|
226
|
+
subcomponents.values do |sub|
|
227
|
+
export_subcomponent_to(export_subcomponent_to, sub)
|
228
|
+
end
|
229
|
+
export_stream.puts("END:#{entity_name}")
|
230
|
+
end
|
231
|
+
|
232
|
+
# Export this single component as an iCalendar component containing only this component and
|
233
|
+
# any required additional components (i.e. VTIMEZONES referenced from this component)
|
234
|
+
# if stream is nil (the default) then this method will return a string,
|
235
|
+
# otherwise stream should be an IO to which the iCalendar file contents will be written
|
236
|
+
def export(stream=nil)
|
237
|
+
wrapper_calendar = Calendar.new
|
238
|
+
wrapper_calendar.add_subcomponent(self)
|
239
|
+
wrapper_calendar.export(stream)
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
Dir[File.dirname(__FILE__) + "/component/*.rb"].sort.each do |path|
|
245
|
+
filename = File.basename(path)
|
246
|
+
require path
|
247
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), %w[.. properties alarm.rb])
|
2
|
+
|
3
|
+
module RiCal
|
4
|
+
|
5
|
+
class Component
|
6
|
+
#- ©2009 Rick DeNatale, All rights reserved. Refer to the file README.txt for the license
|
7
|
+
#
|
8
|
+
# An Alarm component groups properties defining a reminder or alarm associated with an event or to-do
|
9
|
+
# TODO: The Alarm component has complex cardinality restrictions depending on the value of the action property
|
10
|
+
# i.e. audio, display, email, and proc alarms, this is currently not checked or enforced
|
11
|
+
#
|
12
|
+
# to see the property accessing methods for this class see the RiCal::Properties::Alarm module
|
13
|
+
class Alarm < Component
|
14
|
+
include RiCal::Properties::Alarm
|
15
|
+
|
16
|
+
def self.entity_name #:nodoc:
|
17
|
+
"VALARM"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,219 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), %w[.. properties calendar.rb])
|
2
|
+
|
3
|
+
module RiCal
|
4
|
+
class Component
|
5
|
+
#- ©2009 Rick DeNatale, All rights reserved. Refer to the file README.txt for the license
|
6
|
+
#
|
7
|
+
# to see the property accessing methods for this class see the RiCal::Properties::Calendar module
|
8
|
+
class Calendar < Component
|
9
|
+
include RiCal::Properties::Calendar
|
10
|
+
attr_reader :tz_source #:nodoc:
|
11
|
+
|
12
|
+
def initialize(parent=nil, &init_block) #:nodoc:
|
13
|
+
@tz_source = 'TZINFO' # Until otherwise told
|
14
|
+
super
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.entity_name #:nodoc:
|
18
|
+
"VCALENDAR"
|
19
|
+
end
|
20
|
+
|
21
|
+
def tz_info_source? #:nodoc:
|
22
|
+
@tz_source == 'TZINFO'
|
23
|
+
end
|
24
|
+
|
25
|
+
def required_timezones # :nodoc:
|
26
|
+
@required_timezones ||= RequiredTimezones.new
|
27
|
+
end
|
28
|
+
|
29
|
+
def subcomponent_class # :nodoc:
|
30
|
+
{
|
31
|
+
:event => Event,
|
32
|
+
:todo => Todo,
|
33
|
+
:journal => Journal,
|
34
|
+
:freebusy => Freebusy,
|
35
|
+
:timezone => Timezone,
|
36
|
+
}
|
37
|
+
end
|
38
|
+
|
39
|
+
def export_properties_to(export_stream) # :nodoc:
|
40
|
+
prodid_property.params["X-RICAL-TZSOURCE"] = @tz_source
|
41
|
+
export_prop_to(export_stream, "PRODID", prodid_property)
|
42
|
+
export_prop_to(export_stream, "CALSCALE", calscale_property)
|
43
|
+
export_prop_to(export_stream, "VERSION", version_property)
|
44
|
+
export_prop_to(export_stream, "METHOD", method_property)
|
45
|
+
end
|
46
|
+
|
47
|
+
def prodid_property_from_string(line) # :nodoc:
|
48
|
+
result = super
|
49
|
+
@tz_source = prodid_property.params["X-RICAL-TZSOURCE"]
|
50
|
+
result
|
51
|
+
end
|
52
|
+
|
53
|
+
# Return the default time zone identifier for this calendar
|
54
|
+
def default_tzid
|
55
|
+
@default_tzid || PropertyValue::DateTime.default_tzid
|
56
|
+
end
|
57
|
+
|
58
|
+
# Set the default time zone identifier for this calendar
|
59
|
+
# To set the default to floating times use a value of :floating
|
60
|
+
def default_tzid=(value)
|
61
|
+
@default_tzid=value
|
62
|
+
end
|
63
|
+
|
64
|
+
# return an array of event components contained within this Calendar
|
65
|
+
def events
|
66
|
+
subcomponents["VEVENT"]
|
67
|
+
end
|
68
|
+
|
69
|
+
# add an event to the calendar
|
70
|
+
def add_subcomponent(component)
|
71
|
+
super(component)
|
72
|
+
component.add_date_times_to(required_timezones) if tz_info_source?
|
73
|
+
end
|
74
|
+
|
75
|
+
# return an array of todo components contained within this Calendar
|
76
|
+
def todos
|
77
|
+
subcomponents["VTODO"]
|
78
|
+
end
|
79
|
+
|
80
|
+
# return an array of journal components contained within this Calendar
|
81
|
+
def journals
|
82
|
+
subcomponents["VJOURNAL"]
|
83
|
+
end
|
84
|
+
|
85
|
+
# return an array of freebusy components contained within this Calendar
|
86
|
+
def freebusys
|
87
|
+
subcomponents["VFREEBUSY"]
|
88
|
+
end
|
89
|
+
|
90
|
+
class TimezoneID #:nodoc:
|
91
|
+
attr_reader :identifier, :calendar
|
92
|
+
def initialize(identifier, calendar)
|
93
|
+
self.identifier, self.calendar = identifier, calendar
|
94
|
+
end
|
95
|
+
|
96
|
+
def tzinfo_timezone
|
97
|
+
nil
|
98
|
+
end
|
99
|
+
|
100
|
+
def resolved
|
101
|
+
calendar.find_timezone(identifier)
|
102
|
+
end
|
103
|
+
|
104
|
+
def local_to_utc(local)
|
105
|
+
resolved.local_to_utc(date_time_prop)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# return an array of timezone components contained within this calendar
|
110
|
+
def timezones
|
111
|
+
subcomponents["VTIMEZONE"]
|
112
|
+
end
|
113
|
+
|
114
|
+
class TZInfoWrapper #:nodoc:
|
115
|
+
attr_reader :tzinfo, :calendar #:nodoc:
|
116
|
+
def initialize(tzinfo, calendar) #:nodoc:
|
117
|
+
@tzinfo = tzinfo
|
118
|
+
@calendar = calendar
|
119
|
+
end
|
120
|
+
|
121
|
+
def identifier #:nodoc:
|
122
|
+
tzinfo.identifier
|
123
|
+
end
|
124
|
+
|
125
|
+
def local_date_time(ruby_time, tzid) #:nodoc:
|
126
|
+
RiCal::PropertyValue::DateTime.new(calendar, :value => ruby_time.strftime("%Y%m%dT%H%M%S"), :params => {'TZID' => tzid})
|
127
|
+
end
|
128
|
+
|
129
|
+
def utc_date_time(ruby_time) #:nodoc
|
130
|
+
RiCal::PropertyValue::DateTime.new(calendar, :value => ruby_time.strftime("%Y%m%dT%H%M%SZ"))
|
131
|
+
end
|
132
|
+
|
133
|
+
def local_to_utc(utc) #:nodoc:
|
134
|
+
utc_date_time(tzinfo.local_to_utc(utc.to_ri_cal_ruby_value))
|
135
|
+
end
|
136
|
+
|
137
|
+
def utc_to_local(local) #:nodoc:
|
138
|
+
local_date_time(tzinfo.utc_to_local(local.to_ri_cal_ruby_value), tzinfo.identifier)
|
139
|
+
end
|
140
|
+
|
141
|
+
|
142
|
+
def rational_utc_offset(local)
|
143
|
+
Rational(tzinfo.period_for_local(local, true).utc_total_offset, 3600) / 24
|
144
|
+
end
|
145
|
+
|
146
|
+
end
|
147
|
+
|
148
|
+
def find_timezone(identifier) #:nodoc:
|
149
|
+
if tz_info_source?
|
150
|
+
begin
|
151
|
+
TZInfoWrapper.new(TZInfo::Timezone.get(identifier), self)
|
152
|
+
rescue ::TZInfo::InvalidTimezoneIdentifier => ex
|
153
|
+
raise RiCal::InvalidTimezoneIdentifier.invalid_tzinfo_identifier(identifier)
|
154
|
+
end
|
155
|
+
else
|
156
|
+
result = timezones.find {|tz| tz.tzid == identifier}
|
157
|
+
raise RiCal::InvalidTimezoneIdentifier.not_found_in_calendar(identifier) unless result
|
158
|
+
result
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
def export_required_timezones(export_stream) # :nodoc:
|
163
|
+
required_timezones.export_to(export_stream)
|
164
|
+
end
|
165
|
+
|
166
|
+
class FoldingStream #:nodoc:
|
167
|
+
attr_reader :stream #:nodoc:
|
168
|
+
def initialize(stream) #:nodoc:
|
169
|
+
@stream = stream || StringIO.new
|
170
|
+
end
|
171
|
+
|
172
|
+
def string #:nodoc:
|
173
|
+
stream.string
|
174
|
+
end
|
175
|
+
|
176
|
+
def fold(string) #:nodoc:
|
177
|
+
stream.puts(string[0,73])
|
178
|
+
string = string[73..-1]
|
179
|
+
while string
|
180
|
+
stream.puts " #{string[0, 72]}"
|
181
|
+
string = string[72..-1]
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
def puts(*strings) #:nodoc:
|
186
|
+
strings.each do |string|
|
187
|
+
string.split("\n").each do |line|
|
188
|
+
fold(line)
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
# Export this calendar as an iCalendar file.
|
195
|
+
# if to is nil (the default) then this method will return a string,
|
196
|
+
# otherwise to should be an IO to which the iCalendar file contents will be written
|
197
|
+
def export(to=nil)
|
198
|
+
export_stream = FoldingStream.new(to)
|
199
|
+
export_stream.puts("BEGIN:VCALENDAR")
|
200
|
+
#TODO: right now I'm assuming that all timezones are internal what happens when we export
|
201
|
+
# an imported calendar.
|
202
|
+
export_properties_to(export_stream)
|
203
|
+
export_x_properties_to(export_stream)
|
204
|
+
export_required_timezones(export_stream)
|
205
|
+
export_subcomponent_to(export_stream, events)
|
206
|
+
export_subcomponent_to(export_stream, todos)
|
207
|
+
export_subcomponent_to(export_stream, journals)
|
208
|
+
export_subcomponent_to(export_stream, freebusys)
|
209
|
+
export_stream.puts("END:VCALENDAR")
|
210
|
+
if to
|
211
|
+
nil
|
212
|
+
else
|
213
|
+
export_stream.string
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|