paulsm-icalendar 1.1.0.4
Sign up to get free protection for your applications and to get access to all the features.
- data/COPYING +56 -0
- data/GPL +340 -0
- data/README +266 -0
- data/Rakefile +109 -0
- data/docs/rfcs/itip_notes.txt +69 -0
- data/docs/rfcs/rfc2425.pdf +0 -0
- data/docs/rfcs/rfc2426.pdf +0 -0
- data/docs/rfcs/rfc2445.pdf +0 -0
- data/docs/rfcs/rfc2446.pdf +0 -0
- data/docs/rfcs/rfc2447.pdf +0 -0
- data/docs/rfcs/rfc3283.txt +738 -0
- data/examples/create_cal.rb +45 -0
- data/examples/parse_cal.rb +20 -0
- data/examples/single_event.ics +18 -0
- data/lib/hash_attrs.rb +34 -0
- data/lib/icalendar.rb +39 -0
- data/lib/icalendar/base.rb +43 -0
- data/lib/icalendar/calendar.rb +113 -0
- data/lib/icalendar/component.rb +442 -0
- data/lib/icalendar/component/alarm.rb +44 -0
- data/lib/icalendar/component/event.rb +129 -0
- data/lib/icalendar/component/freebusy.rb +38 -0
- data/lib/icalendar/component/journal.rb +61 -0
- data/lib/icalendar/component/timezone.rb +105 -0
- data/lib/icalendar/component/todo.rb +64 -0
- data/lib/icalendar/conversions.rb +150 -0
- data/lib/icalendar/helpers.rb +109 -0
- data/lib/icalendar/parameter.rb +33 -0
- data/lib/icalendar/parser.rb +396 -0
- data/lib/icalendar/rrule.rb +126 -0
- data/lib/icalendar/tzinfo.rb +121 -0
- data/lib/meta.rb +32 -0
- data/test/calendar_test.rb +71 -0
- data/test/component/event_test.rb +256 -0
- data/test/component/timezone_test.rb +67 -0
- data/test/component/todo_test.rb +13 -0
- data/test/component_test.rb +76 -0
- data/test/conversions_test.rb +97 -0
- data/test/coverage/STUB +0 -0
- data/test/fixtures/folding.ics +23 -0
- data/test/fixtures/life.ics +46 -0
- data/test/fixtures/simplecal.ics +119 -0
- data/test/fixtures/single_event.ics +23 -0
- data/test/interactive.rb +17 -0
- data/test/parameter_test.rb +29 -0
- data/test/parser_test.rb +84 -0
- data/test/read_write.rb +23 -0
- metadata +108 -0
@@ -0,0 +1,45 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
## Need this so we can require the library from the samples directory
|
3
|
+
$:.unshift(File.dirname(__FILE__) + '/../lib')
|
4
|
+
|
5
|
+
require 'rubygems' # Unless you install from the tarball or zip.
|
6
|
+
require 'icalendar'
|
7
|
+
require 'date'
|
8
|
+
|
9
|
+
include Icalendar # Probably do this in your class to limit namespace overlap
|
10
|
+
|
11
|
+
## Creating calendars and events is easy.
|
12
|
+
|
13
|
+
# Create a calendar with an event (standard method)
|
14
|
+
cal = Calendar.new
|
15
|
+
cal.event do
|
16
|
+
dtstart Date.new(2005, 04, 29)
|
17
|
+
dtend Date.new(2005, 04, 28)
|
18
|
+
summary "Meeting with the man."
|
19
|
+
description "Have a long lunch meeting and decide nothing..."
|
20
|
+
klass "PRIVATE"
|
21
|
+
end
|
22
|
+
|
23
|
+
## Or you can make events like this
|
24
|
+
event = Event.new
|
25
|
+
event.start = DateTime.civil(2006, 6, 23, 8, 30)
|
26
|
+
event.summary = "A great event!"
|
27
|
+
cal.add_event(event)
|
28
|
+
|
29
|
+
event2 = cal.event # This automatically adds the event to the calendar
|
30
|
+
event2.start = DateTime.civil(2006, 6, 24, 8, 30)
|
31
|
+
event2.summary = "Another great event!"
|
32
|
+
|
33
|
+
# Now with support for property parameters
|
34
|
+
params = {"ALTREP" =>['"http://my.language.net"'], "LANGUAGE" => ["SPANISH"]}
|
35
|
+
|
36
|
+
cal.event do
|
37
|
+
dtstart Date.new(2005, 04, 29)
|
38
|
+
dtend Date.new(2005, 04, 28)
|
39
|
+
summary "This is a summary with params.", params
|
40
|
+
end
|
41
|
+
|
42
|
+
# We can output the calendar as a string to write to a file,
|
43
|
+
# network port, database etc.
|
44
|
+
cal_string = cal.to_ical
|
45
|
+
puts cal_string
|
@@ -0,0 +1,20 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
## Need this so we can require the library from the samples directory
|
3
|
+
$:.unshift(File.dirname(__FILE__) + '/../lib')
|
4
|
+
|
5
|
+
require 'icalendar'
|
6
|
+
require 'date'
|
7
|
+
|
8
|
+
# Open a file or string to parse
|
9
|
+
cal_file = File.open("../test/life.ics")
|
10
|
+
|
11
|
+
# Parser returns an array of calendars because a single file
|
12
|
+
# can have multiple calendar objects.
|
13
|
+
cals = Icalendar::parse(cal_file)
|
14
|
+
cal = cals.first
|
15
|
+
|
16
|
+
# Now you can access the cal object in just the same way I created it
|
17
|
+
event = cal.events.first
|
18
|
+
|
19
|
+
puts "start date-time: " + event.dtstart.to_s
|
20
|
+
puts "summary: " + event.summary
|
@@ -0,0 +1,18 @@
|
|
1
|
+
BEGIN:VCALENDAR
|
2
|
+
VERSION:2.0
|
3
|
+
PRODID:bsprodidfortestabc123
|
4
|
+
BEGIN:VEVENT
|
5
|
+
UID:bsuidfortestabc123
|
6
|
+
SUMMARY:This is a really long summary
|
7
|
+
to test the method of unfolding lines
|
8
|
+
so I'm just going to ma
|
9
|
+
ke it
|
10
|
+
a whol
|
11
|
+
e
|
12
|
+
bunch of lines.
|
13
|
+
CLASS:PRIVATE
|
14
|
+
DTSTART;TZID=US-Mountain:20050120T170000
|
15
|
+
DTEND:20050120T184500
|
16
|
+
DTSTAMP:20050118T211523Z
|
17
|
+
END:VEVENT
|
18
|
+
END:VCALENDAR
|
data/lib/hash_attrs.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# A module which adds some generators for hash based accessors.
|
2
|
+
module HashAttrs
|
3
|
+
|
4
|
+
def hash_reader(hash_sym, syms)
|
5
|
+
syms.each do |id|
|
6
|
+
id = id.to_s.downcase
|
7
|
+
func = Proc.new do
|
8
|
+
hash = instance_variable_get(hash_sym)
|
9
|
+
hash[id.to_sym]
|
10
|
+
end
|
11
|
+
|
12
|
+
self.send(:define_method, id, func)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def hash_writer(hash_sym, syms)
|
17
|
+
syms.each do |id|
|
18
|
+
id = id.to_s.downcase
|
19
|
+
|
20
|
+
func = Proc.new do |val|
|
21
|
+
hash = instance_variable_get(hash_sym)
|
22
|
+
hash[id.to_sym] = val
|
23
|
+
end
|
24
|
+
|
25
|
+
self.send(:define_method, id+'=', func)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def hash_accessor(hash, *syms)
|
30
|
+
hash_reader(hash, syms)
|
31
|
+
hash_writer(hash, syms)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
data/lib/icalendar.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
=begin
|
2
|
+
Copyright (C) 2005 Jeff Rose
|
3
|
+
|
4
|
+
This library is free software; you can redistribute it and/or modify it
|
5
|
+
under the same terms as the ruby language itself, see the file COPYING for
|
6
|
+
details.
|
7
|
+
=end
|
8
|
+
|
9
|
+
$:.unshift(File.dirname(__FILE__))
|
10
|
+
|
11
|
+
### Base classes and mixin modules
|
12
|
+
|
13
|
+
# to_ical methods for built-in classes
|
14
|
+
require 'icalendar/conversions'
|
15
|
+
|
16
|
+
# Meta-programming helper methods
|
17
|
+
require 'meta'
|
18
|
+
|
19
|
+
# Hash attributes mixin module
|
20
|
+
require 'hash_attrs'
|
21
|
+
|
22
|
+
require 'icalendar/base'
|
23
|
+
require 'icalendar/component'
|
24
|
+
require 'icalendar/rrule'
|
25
|
+
|
26
|
+
# Calendar and components
|
27
|
+
require 'icalendar/calendar'
|
28
|
+
require 'icalendar/component/event'
|
29
|
+
require 'icalendar/component/journal'
|
30
|
+
require 'icalendar/component/todo'
|
31
|
+
require 'icalendar/component/freebusy'
|
32
|
+
require 'icalendar/component/timezone'
|
33
|
+
require 'icalendar/component/alarm'
|
34
|
+
|
35
|
+
# Calendar parser
|
36
|
+
require 'icalendar/parser'
|
37
|
+
|
38
|
+
# TZINFO support
|
39
|
+
# require 'icalendar/tzinfo'
|
@@ -0,0 +1,43 @@
|
|
1
|
+
=begin
|
2
|
+
Copyright (C) 2005 Jeff Rose
|
3
|
+
|
4
|
+
This library is free software; you can redistribute it and/or modify it
|
5
|
+
under the same terms as the ruby language itself, see the file COPYING for
|
6
|
+
details.
|
7
|
+
=end
|
8
|
+
require 'logger'
|
9
|
+
|
10
|
+
module Icalendar #:nodoc:
|
11
|
+
|
12
|
+
# A simple error class to differentiate iCalendar library exceptions
|
13
|
+
# from ruby language exceptions or others.
|
14
|
+
class IcalendarError < StandardError #:nodoc:
|
15
|
+
end
|
16
|
+
|
17
|
+
# Exception used when the library encounters a bogus calendar component.
|
18
|
+
class UnknownComponentClass < IcalendarError
|
19
|
+
end
|
20
|
+
|
21
|
+
# Exception used when the library encounters a bogus property type.
|
22
|
+
class UnknownPropertyMethod< IcalendarError
|
23
|
+
end
|
24
|
+
|
25
|
+
# Exception used when the library encounters a bogus property value.
|
26
|
+
class InvalidPropertyValue < IcalendarError
|
27
|
+
end
|
28
|
+
|
29
|
+
# This class serves as the base class for just about everything in
|
30
|
+
# the library so that the logging system can be configured in one place.
|
31
|
+
class Base
|
32
|
+
@@logger = Logger.new(STDERR)
|
33
|
+
@@logger.level = Logger::FATAL
|
34
|
+
|
35
|
+
def self.debug
|
36
|
+
@@logger.level = Logger::DEBUG
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.quiet
|
40
|
+
@@logger.level = Logger::FATAL
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
=begin
|
2
|
+
Copyright (C) 2005 Jeff Rose
|
3
|
+
|
4
|
+
This library is free software; you can redistribute it and/or modify it
|
5
|
+
under the same terms as the ruby language itself, see the file COPYING for
|
6
|
+
details.
|
7
|
+
=end
|
8
|
+
|
9
|
+
module Icalendar
|
10
|
+
|
11
|
+
class Calendar < Component
|
12
|
+
ical_component :events, :todos, :journals, :freebusys, :timezones
|
13
|
+
|
14
|
+
ical_property :calscale, :calendar_scale
|
15
|
+
ical_property :prodid, :product_id
|
16
|
+
ical_property :version
|
17
|
+
ical_property :ip_method
|
18
|
+
|
19
|
+
def initialize()
|
20
|
+
super("VCALENDAR")
|
21
|
+
|
22
|
+
# Set some defaults
|
23
|
+
self.calscale = "GREGORIAN" # Who knows, but this is the only one in the spec.
|
24
|
+
self.prodid = "iCalendar-Ruby" # Current product... Should be overwritten by apps that use the library
|
25
|
+
self.version = "2.0" # Version of the specification
|
26
|
+
end
|
27
|
+
|
28
|
+
def event(&block)
|
29
|
+
e = Event.new
|
30
|
+
# Note: I'm not sure this is the best way to pass this down, but it works
|
31
|
+
e.tzid = self.timezones[0].tzid if self.timezones.length > 0
|
32
|
+
|
33
|
+
self.add_component e
|
34
|
+
|
35
|
+
if block
|
36
|
+
e.instance_eval &block
|
37
|
+
if e.tzid
|
38
|
+
e.dtstart.ical_params = { "TZID" => e.tzid }
|
39
|
+
e.dtend.ical_params = { "TZID" => e.tzid }
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
e
|
44
|
+
end
|
45
|
+
|
46
|
+
def find_event(uid)
|
47
|
+
self.events.find {|e| e.uid == uid}
|
48
|
+
end
|
49
|
+
|
50
|
+
def todo(&block)
|
51
|
+
e = Todo.new
|
52
|
+
self.add_component e
|
53
|
+
|
54
|
+
e.instance_eval &block if block
|
55
|
+
|
56
|
+
e
|
57
|
+
end
|
58
|
+
|
59
|
+
def find_todo(uid)
|
60
|
+
self.todos.find {|t| t.uid == uid}
|
61
|
+
end
|
62
|
+
|
63
|
+
def journal(&block)
|
64
|
+
e = Journal.new
|
65
|
+
self.add_component e
|
66
|
+
|
67
|
+
e.instance_eval &block if block
|
68
|
+
|
69
|
+
e
|
70
|
+
end
|
71
|
+
|
72
|
+
def find_journal(uid)
|
73
|
+
self.journals.find {|j| j.uid == uid}
|
74
|
+
end
|
75
|
+
|
76
|
+
def freebusy(&block)
|
77
|
+
e = Freebusy.new
|
78
|
+
self.add_component e
|
79
|
+
|
80
|
+
e.instance_eval &block if block
|
81
|
+
|
82
|
+
e
|
83
|
+
end
|
84
|
+
|
85
|
+
def find_freebusy(uid)
|
86
|
+
self.freebusys.find {|f| f.uid == uid}
|
87
|
+
end
|
88
|
+
|
89
|
+
def timezone(&block)
|
90
|
+
e = Timezone.new
|
91
|
+
self.add_component e
|
92
|
+
|
93
|
+
e.instance_eval &block if block
|
94
|
+
|
95
|
+
e
|
96
|
+
end
|
97
|
+
|
98
|
+
# The "PUBLISH" method in a "VEVENT" calendar component is an
|
99
|
+
# unsolicited posting of an iCalendar object. Any CU may add published
|
100
|
+
# components to their calendar. The "Organizer" MUST be present in a
|
101
|
+
# published iCalendar component. "Attendees" MUST NOT be present. Its
|
102
|
+
# expected usage is for encapsulating an arbitrary event as an
|
103
|
+
# iCalendar object. The "Organizer" may subsequently update (with
|
104
|
+
# another "PUBLISH" method), add instances to (with an "ADD" method),
|
105
|
+
# or cancel (with a "CANCEL" method) a previously published "VEVENT"
|
106
|
+
# calendar component.
|
107
|
+
def publish
|
108
|
+
self.ip_method = "PUBLISH"
|
109
|
+
end
|
110
|
+
|
111
|
+
end # class Calendar
|
112
|
+
|
113
|
+
end # module Icalendar
|
@@ -0,0 +1,442 @@
|
|
1
|
+
=begin
|
2
|
+
Copyright (C) 2005 Jeff Rose
|
3
|
+
|
4
|
+
This library is free software; you can redistribute it and/or modify it
|
5
|
+
under the same terms as the ruby language itself, see the file COPYING for
|
6
|
+
details.
|
7
|
+
=end
|
8
|
+
|
9
|
+
module Icalendar
|
10
|
+
require 'socket'
|
11
|
+
|
12
|
+
MAX_LINE_LENGTH = 75
|
13
|
+
|
14
|
+
class Geo < Icalendar::Base
|
15
|
+
attr_accessor :latitude, :longitude
|
16
|
+
alias :lat :latitude
|
17
|
+
alias :long :longitude
|
18
|
+
|
19
|
+
def initialize(lat, long)
|
20
|
+
@lat = lat
|
21
|
+
@long = long
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_ical
|
25
|
+
"#{@lat.to_ical};#{@long.to_ical}"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# The body of the iCalendar object consists of a sequence of calendar
|
30
|
+
# properties and one or more calendar components. The calendar
|
31
|
+
# properties are attributes that apply to the calendar as a whole. The
|
32
|
+
# calendar components are collections of properties that express a
|
33
|
+
# particular calendar semantic. For example, the calendar component can
|
34
|
+
# specify an Event, a Todo, a Journal entry, Timezone information, or
|
35
|
+
# Freebusy time information, or an Alarm.
|
36
|
+
class Component < Icalendar::Base
|
37
|
+
|
38
|
+
meta_include HashAttrs
|
39
|
+
|
40
|
+
attr_reader :name
|
41
|
+
attr_accessor :properties
|
42
|
+
|
43
|
+
@@multi_properties = {}
|
44
|
+
@@multiline_properties = {}
|
45
|
+
|
46
|
+
def initialize(name)
|
47
|
+
@name = name
|
48
|
+
@components = Hash.new([])
|
49
|
+
@properties = {}
|
50
|
+
|
51
|
+
@@logger.info("New #{@name[1,@name.size].capitalize}...")
|
52
|
+
end
|
53
|
+
|
54
|
+
# Add a sub-component to the current component object.
|
55
|
+
def add_component(component)
|
56
|
+
key = (component.class.to_s.downcase + 's').gsub('icalendar::', '').to_sym
|
57
|
+
|
58
|
+
unless @components.has_key? key
|
59
|
+
@components[key] = []
|
60
|
+
end
|
61
|
+
|
62
|
+
@components[key] << component
|
63
|
+
end
|
64
|
+
|
65
|
+
# Add a component to the calendar.
|
66
|
+
alias add add_component
|
67
|
+
|
68
|
+
# Add an event to the calendar.
|
69
|
+
alias add_event add_component
|
70
|
+
|
71
|
+
# Add a todo item to the calendar.
|
72
|
+
alias add_todo add_component
|
73
|
+
|
74
|
+
# Add a journal item to the calendar.
|
75
|
+
alias add_journal add_component
|
76
|
+
|
77
|
+
def remove_component(component)
|
78
|
+
key = (component.class.to_s.downcase + 's').gsub('icalendar::', '').to_sym
|
79
|
+
|
80
|
+
if @components.has_key? key
|
81
|
+
@components[key].delete(component)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# Remove a component from the calendar.
|
86
|
+
alias remove remove_component
|
87
|
+
|
88
|
+
# Remove an event from the calendar.
|
89
|
+
alias remove_event remove_component
|
90
|
+
|
91
|
+
# Remove a todo item from the calendar.
|
92
|
+
alias remove_todo remove_component
|
93
|
+
|
94
|
+
# Remove a journal item from the calendar.
|
95
|
+
alias remove_journal remove_component
|
96
|
+
|
97
|
+
# Used to generate unique component ids
|
98
|
+
def new_uid
|
99
|
+
"#{DateTime.now}_#{rand(999999999)}@#{Socket.gethostname}"
|
100
|
+
end
|
101
|
+
|
102
|
+
# Output in the icalendar format
|
103
|
+
def to_ical
|
104
|
+
print_component do
|
105
|
+
s = ""
|
106
|
+
@components.each_value do |comps|
|
107
|
+
comps.each { |component| s << component.to_ical }
|
108
|
+
end
|
109
|
+
s
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# Print this icalendar component
|
114
|
+
def print_component
|
115
|
+
# Begin a new component
|
116
|
+
"BEGIN:#{@name.upcase}\r\n" +
|
117
|
+
|
118
|
+
# Then the properties
|
119
|
+
print_properties +
|
120
|
+
|
121
|
+
# sub components
|
122
|
+
yield +
|
123
|
+
|
124
|
+
# End of this component
|
125
|
+
"END:#{@name.upcase}\r\n"
|
126
|
+
end
|
127
|
+
|
128
|
+
# Print this components properties
|
129
|
+
def print_properties
|
130
|
+
s = ""
|
131
|
+
|
132
|
+
@properties.each do |key,val|
|
133
|
+
# Take out underscore for property names that conflicted
|
134
|
+
# with built-in words.
|
135
|
+
if key =~ /ip_.*/
|
136
|
+
key = key[3..-1]
|
137
|
+
end
|
138
|
+
|
139
|
+
# Property name
|
140
|
+
unless multiline_property?(key)
|
141
|
+
prelude = "#{key.gsub(/_/, '-').upcase}" +
|
142
|
+
|
143
|
+
# Possible parameters
|
144
|
+
print_parameters(val)
|
145
|
+
|
146
|
+
# Property value
|
147
|
+
value = ":#{val.to_ical}"
|
148
|
+
add_sliced_text(s,prelude+escape_chars(value))
|
149
|
+
else
|
150
|
+
prelude = "#{key.gsub(/_/, '-').upcase}"
|
151
|
+
val.each do |v|
|
152
|
+
params = print_parameters(v)
|
153
|
+
value = ":#{v.to_ical}"
|
154
|
+
add_sliced_text(s,prelude + params + escape_chars(value))
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
s
|
159
|
+
end
|
160
|
+
|
161
|
+
def escape_chars(value)
|
162
|
+
value.gsub("\\", "\\\\").gsub("\n", "\\n").gsub(",", "\\,").gsub(";", "\\;")
|
163
|
+
end
|
164
|
+
|
165
|
+
def add_sliced_text(add_to,escaped)
|
166
|
+
escaped = escaped.split('') # split is unicdoe-aware when `$KCODE = 'u'`
|
167
|
+
add_to << escaped.slice!(0,MAX_LINE_LENGTH).to_s << "\r\n " while escaped.length != 0 # shift(MAX_LINE_LENGTH) does not work with ruby 1.8.6
|
168
|
+
add_to.gsub!(/ *$/, '')
|
169
|
+
end
|
170
|
+
|
171
|
+
# Print the parameters for a specific property
|
172
|
+
def print_parameters(val)
|
173
|
+
s = ""
|
174
|
+
return s unless val.respond_to?(:ical_params) and not val.ical_params.nil?
|
175
|
+
|
176
|
+
val.ical_params.each do |key,val|
|
177
|
+
s << ";#{key}"
|
178
|
+
val = [ val ] unless val.is_a?(Array)
|
179
|
+
|
180
|
+
# Possible parameter values
|
181
|
+
unless val.empty?
|
182
|
+
s << "="
|
183
|
+
sep = "" # First entry comes after = sign, but then we need commas
|
184
|
+
val.each do |pval|
|
185
|
+
if pval.respond_to? :to_ical
|
186
|
+
s << sep << pval.to_ical
|
187
|
+
sep = ","
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
s
|
193
|
+
end
|
194
|
+
|
195
|
+
# TODO: Look into the x-property, x-param stuff...
|
196
|
+
# This would really only be needed for subclassing to add additional
|
197
|
+
# properties to an application using the API.
|
198
|
+
def custom_property(name, value)
|
199
|
+
@properties[name] = value
|
200
|
+
end
|
201
|
+
|
202
|
+
def multi_property?(name)
|
203
|
+
@@multi_properties.has_key?(name.downcase)
|
204
|
+
end
|
205
|
+
|
206
|
+
def multiline_property?(name)
|
207
|
+
@@multiline_properties.has_key?(name.downcase)
|
208
|
+
end
|
209
|
+
|
210
|
+
# Make it protected so we can monitor usage...
|
211
|
+
protected
|
212
|
+
|
213
|
+
def Component.ical_component(*syms)
|
214
|
+
hash_accessor :@components, *syms
|
215
|
+
end
|
216
|
+
|
217
|
+
# Define a set of methods supporting a new property
|
218
|
+
def Component.ical_property(property, alias_name = nil, prop_name = nil)
|
219
|
+
property = "#{property}".strip.downcase
|
220
|
+
alias_name = "#{alias_name}".strip.downcase unless alias_name.nil?
|
221
|
+
# If a prop_name was given then we use that for the actual storage
|
222
|
+
property = "#{prop_name}".strip.downcase unless prop_name.nil?
|
223
|
+
|
224
|
+
generate_getter(property, alias_name)
|
225
|
+
generate_setter(property, alias_name)
|
226
|
+
generate_query(property, alias_name)
|
227
|
+
end
|
228
|
+
|
229
|
+
# Define a set of methods defining a new property, which
|
230
|
+
# supports multiple values for the same property name.
|
231
|
+
def Component.ical_multi_property(property, singular, plural)
|
232
|
+
property = "#{property}".strip.downcase.gsub(/-/, '_')
|
233
|
+
plural = "#{plural}".strip.downcase
|
234
|
+
|
235
|
+
# Set this key so the parser knows to use an array for
|
236
|
+
# storing this property type.
|
237
|
+
@@multi_properties["#{property}"] = true
|
238
|
+
|
239
|
+
generate_multi_getter(property, plural)
|
240
|
+
generate_multi_setter(property, plural)
|
241
|
+
generate_multi_query(property, plural)
|
242
|
+
generate_multi_adder(property, singular)
|
243
|
+
generate_multi_remover(property, singular)
|
244
|
+
end
|
245
|
+
|
246
|
+
# Define a set of methods defining a new property, which
|
247
|
+
# supports multiple values in multiple lines with same property name
|
248
|
+
def Component.ical_multiline_property(property, singular, plural)
|
249
|
+
@@multiline_properties["#{property}"] = true
|
250
|
+
ical_multi_property(property, singular, plural)
|
251
|
+
end
|
252
|
+
|
253
|
+
|
254
|
+
private
|
255
|
+
|
256
|
+
def Component.generate_getter(property, alias_name)
|
257
|
+
unless instance_methods.include? property
|
258
|
+
code = <<-code
|
259
|
+
def #{property}(val = nil, params = nil)
|
260
|
+
return @properties["#{property}"] if val.nil?
|
261
|
+
|
262
|
+
unless val.respond_to?(:to_ical)
|
263
|
+
raise(NotImplementedError, "Value of type (" + val.class.to_s + ") does not support to_ical method!")
|
264
|
+
end
|
265
|
+
|
266
|
+
unless params.nil?
|
267
|
+
# Extend with the parameter methods only if we have to...
|
268
|
+
unless val.respond_to?(:ical_params)
|
269
|
+
val.class.class_eval { attr_accessor :ical_params }
|
270
|
+
end
|
271
|
+
val.ical_params = params
|
272
|
+
end
|
273
|
+
|
274
|
+
@properties["#{property}"] = val
|
275
|
+
end
|
276
|
+
code
|
277
|
+
|
278
|
+
class_eval code, "component.rb", 219
|
279
|
+
alias_method("#{alias_name}", "#{property}") unless alias_name.nil?
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
def Component.generate_setter(property, alias_name)
|
284
|
+
setter = property + '='
|
285
|
+
unless instance_methods.include? setter
|
286
|
+
code = <<-code
|
287
|
+
def #{setter}(val)
|
288
|
+
#{property}(val)
|
289
|
+
end
|
290
|
+
code
|
291
|
+
|
292
|
+
class_eval code, "component.rb", 233
|
293
|
+
alias_method("#{alias_name}=", "#{property+'='}") unless alias_name.nil?
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
def Component.generate_query(property, alias_name)
|
298
|
+
query = "#{property}?"
|
299
|
+
unless instance_methods.include? query
|
300
|
+
code = <<-code
|
301
|
+
def #{query}
|
302
|
+
@properties.has_key?("#{property.downcase}")
|
303
|
+
end
|
304
|
+
code
|
305
|
+
|
306
|
+
class_eval code, "component.rb", 226
|
307
|
+
|
308
|
+
alias_method("#{alias_name}\?", "#{query}") unless alias_name.nil?
|
309
|
+
end
|
310
|
+
end
|
311
|
+
|
312
|
+
def Component.generate_multi_getter(property, plural)
|
313
|
+
# Getter for whole array
|
314
|
+
unless instance_methods.include? plural
|
315
|
+
code = <<-code
|
316
|
+
def #{plural}(a = nil)
|
317
|
+
if a.nil?
|
318
|
+
@properties["#{property}"] || []
|
319
|
+
else
|
320
|
+
self.#{plural}=(a)
|
321
|
+
end
|
322
|
+
end
|
323
|
+
code
|
324
|
+
|
325
|
+
class_eval code, "component.rb", 186
|
326
|
+
end
|
327
|
+
end
|
328
|
+
|
329
|
+
def Component.generate_multi_setter(property, plural)
|
330
|
+
# Setter for whole array
|
331
|
+
unless instance_methods.include? plural+'+'
|
332
|
+
code = <<-code
|
333
|
+
def #{plural}=(a)
|
334
|
+
if a.respond_to?(:to_ary)
|
335
|
+
a.to_ary.each do |val|
|
336
|
+
unless val.respond_to?(:to_ical)
|
337
|
+
raise(NotImplementedError, "Property values do not support to_ical method!")
|
338
|
+
end
|
339
|
+
end
|
340
|
+
|
341
|
+
@properties["#{property}"] = a.to_ary
|
342
|
+
else
|
343
|
+
raise ArgumentError, "#{plural} is a multi-property that must be an array! Use the add_[property] method to add single entries."
|
344
|
+
end
|
345
|
+
end
|
346
|
+
code
|
347
|
+
|
348
|
+
class_eval code, "component.rb", 198
|
349
|
+
end
|
350
|
+
end
|
351
|
+
|
352
|
+
def Component.generate_multi_query(property, plural)
|
353
|
+
# Query for any of these properties
|
354
|
+
unless instance_methods.include? plural+'?'
|
355
|
+
code = <<-code
|
356
|
+
def #{plural}?
|
357
|
+
@properties.has_key?("#{property}")
|
358
|
+
end
|
359
|
+
code
|
360
|
+
|
361
|
+
class_eval code, "component.rb", 210
|
362
|
+
end
|
363
|
+
end
|
364
|
+
|
365
|
+
def Component.generate_multi_adder(property, singular)
|
366
|
+
adder = "add_"+singular.to_s
|
367
|
+
# Add another item to this properties array
|
368
|
+
unless instance_methods.include? adder
|
369
|
+
code = <<-code
|
370
|
+
def #{adder}(val, params = {})
|
371
|
+
unless val.respond_to?(:to_ical)
|
372
|
+
raise(NotImplementedError, "Property value object does not support to_ical method!")
|
373
|
+
end
|
374
|
+
|
375
|
+
unless params.nil?
|
376
|
+
# Extend with the parameter methods only if we have to...
|
377
|
+
unless val.respond_to?(:ical_params)
|
378
|
+
val.class.class_eval { attr_accessor :ical_params }
|
379
|
+
end
|
380
|
+
val.ical_params = params
|
381
|
+
end
|
382
|
+
|
383
|
+
if @properties.has_key?("#{property}")
|
384
|
+
@properties["#{property}"] << val
|
385
|
+
else
|
386
|
+
@properties["#{property}"] = [val]
|
387
|
+
end
|
388
|
+
end
|
389
|
+
code
|
390
|
+
|
391
|
+
class_eval code, "component.rb", 289
|
392
|
+
alias_method("add_#{property.downcase}", "#{adder}")
|
393
|
+
end
|
394
|
+
end
|
395
|
+
|
396
|
+
def Component.generate_multi_remover(property, singular)
|
397
|
+
# Remove an item from this properties array
|
398
|
+
unless instance_methods.include? "remove_#{singular}"
|
399
|
+
code = <<-code
|
400
|
+
def remove_#{singular}(a)
|
401
|
+
if @properties.has_key?("#{property}")
|
402
|
+
@properties["#{property}"].delete(a)
|
403
|
+
end
|
404
|
+
end
|
405
|
+
code
|
406
|
+
|
407
|
+
class_eval code, "component.rb", 303
|
408
|
+
alias_method("remove_#{property.downcase}", "remove_#{singular}")
|
409
|
+
end
|
410
|
+
end
|
411
|
+
|
412
|
+
def method_missing(method_name, *args)
|
413
|
+
@@logger.debug("Inside method_missing...")
|
414
|
+
method_name = method_name.to_s.downcase
|
415
|
+
|
416
|
+
unless method_name =~ /x_.*/
|
417
|
+
raise NoMethodError, "Method Name: #{method_name}"
|
418
|
+
end
|
419
|
+
|
420
|
+
# x-properties are accessed with underscore but stored with a dash so
|
421
|
+
# they output correctly and we don't have to special case the
|
422
|
+
# output code, which would require checking every property.
|
423
|
+
if args.size > 0 # Its a setter
|
424
|
+
# Pull off the possible equals
|
425
|
+
@properties[method_name[/x_[^=]*/].gsub('x_', 'x-')] = args.first
|
426
|
+
else # Or its a getter
|
427
|
+
return @properties[method_name.gsub('x_', 'x-')]
|
428
|
+
end
|
429
|
+
end
|
430
|
+
|
431
|
+
public
|
432
|
+
|
433
|
+
def respond_to?(method_name)
|
434
|
+
if method_name.to_s.downcase =~ /x_.*/
|
435
|
+
true
|
436
|
+
else
|
437
|
+
super
|
438
|
+
end
|
439
|
+
end
|
440
|
+
|
441
|
+
end # class Component
|
442
|
+
end
|