curzonj-icalendar 1.0.2
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.
- data/COPYING +56 -0
- data/GPL +340 -0
- data/README +266 -0
- data/Rakefile +110 -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/meta.rb +32 -0
- data/test/calendar_test.rb +71 -0
- data/test/component/event_test.rb +256 -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/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 +105 -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
|