icalendar2 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. data/CHANGELOG.md +2 -0
  2. data/README.md +89 -0
  3. data/lib/icalendar2/calendar.rb +85 -0
  4. data/lib/icalendar2/calendar_property/calscale.rb +9 -0
  5. data/lib/icalendar2/calendar_property/method.rb +9 -0
  6. data/lib/icalendar2/calendar_property/prodid.rb +9 -0
  7. data/lib/icalendar2/calendar_property/version.rb +9 -0
  8. data/lib/icalendar2/calendar_property.rb +12 -0
  9. data/lib/icalendar2/component/base.rb +90 -0
  10. data/lib/icalendar2/component/event.rb +32 -0
  11. data/lib/icalendar2/component.rb +8 -0
  12. data/lib/icalendar2/parameter/altrep.rb +8 -0
  13. data/lib/icalendar2/parameter/base.rb +34 -0
  14. data/lib/icalendar2/parameter/cn.rb +8 -0
  15. data/lib/icalendar2/parameter/cutype.rb +8 -0
  16. data/lib/icalendar2/parameter/delegated_from.rb +8 -0
  17. data/lib/icalendar2/parameter/delegated_to.rb +8 -0
  18. data/lib/icalendar2/parameter/dir.rb +8 -0
  19. data/lib/icalendar2/parameter/encoding.rb +8 -0
  20. data/lib/icalendar2/parameter/fmttype.rb +10 -0
  21. data/lib/icalendar2/parameter.rb +4 -0
  22. data/lib/icalendar2/parser.rb +136 -0
  23. data/lib/icalendar2/property/attach.rb +10 -0
  24. data/lib/icalendar2/property/attendee.rb +8 -0
  25. data/lib/icalendar2/property/base.rb +80 -0
  26. data/lib/icalendar2/property/categories.rb +10 -0
  27. data/lib/icalendar2/property/class.rb +9 -0
  28. data/lib/icalendar2/property/comment.rb +8 -0
  29. data/lib/icalendar2/property/contact.rb +8 -0
  30. data/lib/icalendar2/property/description.rb +9 -0
  31. data/lib/icalendar2/property/dtend.rb +9 -0
  32. data/lib/icalendar2/property/dtstamp.rb +9 -0
  33. data/lib/icalendar2/property/dtstart.rb +9 -0
  34. data/lib/icalendar2/property/exdate.rb +10 -0
  35. data/lib/icalendar2/property/geo.rb +9 -0
  36. data/lib/icalendar2/property/last_mod.rb +9 -0
  37. data/lib/icalendar2/property/location.rb +9 -0
  38. data/lib/icalendar2/property/nil.rb +13 -0
  39. data/lib/icalendar2/property/organizer.rb +9 -0
  40. data/lib/icalendar2/property/priority.rb +9 -0
  41. data/lib/icalendar2/property/rdate.rb +8 -0
  42. data/lib/icalendar2/property/related_to.rb +8 -0
  43. data/lib/icalendar2/property/resources.rb +8 -0
  44. data/lib/icalendar2/property/rrule.rb +10 -0
  45. data/lib/icalendar2/property/rstatus.rb +8 -0
  46. data/lib/icalendar2/property/sequence.rb +9 -0
  47. data/lib/icalendar2/property/summary.rb +9 -0
  48. data/lib/icalendar2/property/uid.rb +9 -0
  49. data/lib/icalendar2/property.rb +9 -0
  50. data/lib/icalendar2/tokens.rb +32 -0
  51. data/lib/icalendar2/value/binary_value.rb +8 -0
  52. data/lib/icalendar2/value/boolean_value.rb +6 -0
  53. data/lib/icalendar2/value/cal_address_value.rb +6 -0
  54. data/lib/icalendar2/value/date_time_value.rb +14 -0
  55. data/lib/icalendar2/value/date_value.rb +14 -0
  56. data/lib/icalendar2/value/duration_value.rb +14 -0
  57. data/lib/icalendar2/value/float_value.rb +7 -0
  58. data/lib/icalendar2/value/integer_value.rb +6 -0
  59. data/lib/icalendar2/value/lat_lon_value.rb +6 -0
  60. data/lib/icalendar2/value/period_value.rb +8 -0
  61. data/lib/icalendar2/value/recur_value.rb +39 -0
  62. data/lib/icalendar2/value/text_value.rb +17 -0
  63. data/lib/icalendar2/value/time_value.rb +6 -0
  64. data/lib/icalendar2/value/uri_value.rb +6 -0
  65. data/lib/icalendar2/value.rb +45 -0
  66. data/lib/icalendar2/version.rb +3 -0
  67. data/lib/icalendar2.rb +74 -0
  68. metadata +131 -0
data/CHANGELOG.md ADDED
@@ -0,0 +1,2 @@
1
+ 0.1.0
2
+ * Initial commit
data/README.md ADDED
@@ -0,0 +1,89 @@
1
+ # icalendar2 - generate, consume and validate iCalendar feeds
2
+
3
+ ## Warning
4
+
5
+ This is NOT production ready yet. It has not been extensively tested, and you
6
+ use it at your own risk.
7
+
8
+ ## Installation
9
+
10
+ If you want to jump right in and try it out, here's how you install it:
11
+
12
+ ``` bash
13
+ gem install icalendar2
14
+ ```
15
+
16
+ Website: https://github.com/ericcf/icalendar2
17
+
18
+ ## Introduction
19
+
20
+ icalendar2 is a Ruby library for parsing, manipulating and generating iCalendar
21
+ objects as described in RFC 5545. Its API is intended to be mostly compatible
22
+ with that of the icalendar gem (https://github.com/sdague/icalendar), at least
23
+ initially.
24
+
25
+ For example:
26
+
27
+ ``` ruby
28
+ require 'rubygems'
29
+ require 'icalendar2'
30
+ include Icalendar2
31
+
32
+ calendar = Calendar.new
33
+ calendar.event do
34
+ dtstart Date.new(2012, 12, 25)
35
+ dtend Date.new(2012, 1, 5)
36
+ summary "12 days of Christmas."
37
+ description "Eat lots of cookies."
38
+ end
39
+ calendar.valid? # true
40
+ puts calendar.to_ical
41
+ # BEGIN:VCALENDAR
42
+ # BEGIN:VEVENT
43
+ # UID:2012-12-12T10:12:45-06:00_253060006@example.com
44
+ # DTSTAMP:20121212T101245
45
+ # DTSTART:20121225
46
+ # DTEND:20120105
47
+ # SUMMARY:12 days of Christmas.
48
+ # DESCRIPTION:Eat lots of cookies.
49
+ # END:VEVENT
50
+ # END:VCALENDAR
51
+
52
+ calendars = Parser.new(calendar.to_ical).parse
53
+ calendars.size # 1
54
+ calendars.first.valid? # true
55
+ ```
56
+
57
+ ## Improvements over/differences with icalendar gem
58
+
59
+ While this gem is based on (and borrows some code from) the icalendar gem, it
60
+ also improves on it in several key areas:
61
+ * No monkey patching of core Ruby classes such as String for conversions
62
+ * Better separation of concerns makes it easier to test and extend
63
+ * Validates calendars and pinpoints errors
64
+ * Uses RSpec for tests
65
+
66
+ ## Testing
67
+
68
+ To run the icalendar2 tests ensure that the rspec gem is installed, and run:
69
+
70
+ ``` bash
71
+ rake test
72
+ ```
73
+
74
+ ## Building the gem
75
+
76
+ ``` bash
77
+ gem build icalendar2.gemspec
78
+ ```
79
+
80
+ ## Contributions
81
+
82
+ I need your help to make this library better. Please use the GitHub issue
83
+ tracker for feature requests and bug reports. The more detail you can provide
84
+ the better.
85
+
86
+ ## License
87
+
88
+ As this is based on the icalendar gem, which employs the Ruby licence, this too
89
+ falls under that license: http://www.ruby-lang.org/en/about/license.txt
@@ -0,0 +1,85 @@
1
+ module Icalendar2
2
+ # See http://tools.ietf.org/html/rfc5545#section-3.4
3
+ class Calendar
4
+ VALUE = "VCALENDAR"
5
+
6
+ def initialize
7
+ @components = {
8
+ Event::VALUE => []
9
+ }
10
+ @properties = {
11
+ :calscale => Property::Nil.new,
12
+ :method => Property::Nil.new,
13
+ :prodid => Property::Nil.new,
14
+ :version => Property::Nil.new
15
+ }
16
+ end
17
+
18
+ def calscale(value = nil)
19
+ set_property(:calscale, value)
20
+ end
21
+
22
+ def method_property(value = nil)
23
+ set_property(:method, value)
24
+ end
25
+
26
+ def prodid(value = nil)
27
+ set_property(:prodid, value)
28
+ end
29
+
30
+ def version(value = nil)
31
+ set_property(:version, value)
32
+ end
33
+
34
+ def set_property(property_name, value, parameters = {})
35
+ property = property_name.downcase.to_sym
36
+ if value.nil?
37
+ @properties[property].value.to_s
38
+ elsif (factory = CalendarProperty.get_factory(property_name))
39
+ if value.is_a? factory
40
+ @properties[property] = value
41
+ else
42
+ @properties[property] = factory.new(value)
43
+ end
44
+ end
45
+ end
46
+
47
+ def events
48
+ @components[Event::VALUE]
49
+ end
50
+
51
+ def event(&block)
52
+ e = Event.new
53
+ e.instance_eval(&block)
54
+ events << e
55
+
56
+ e
57
+ end
58
+
59
+ def add_event(event)
60
+ events << event
61
+ end
62
+
63
+ def to_ical
64
+ str = "#{Tokens::COMPONENT_BEGIN}:#{VALUE}#{Tokens::CRLF}"
65
+ str << body_to_ical
66
+ str << "#{Tokens::COMPONENT_END}:#{VALUE}#{Tokens::CRLF}"
67
+ str.encode("UTF-8")
68
+ end
69
+
70
+ def add_component(component)
71
+ @components[component.class::VALUE] << component
72
+ end
73
+
74
+ def valid?
75
+ events.all?(&:valid?)
76
+ end
77
+
78
+ private
79
+
80
+ def body_to_ical
81
+ str = @properties.values.map(&:to_ical).join
82
+ str << events.map(&:to_ical).join
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,9 @@
1
+ module Icalendar2
2
+ module CalendarProperty
3
+ # See http://tools.ietf.org/html/rfc5545#section-3.7.1
4
+ class Calscale < Property::Base
5
+ name "CALSCALE"
6
+ value :types => [:text]
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module Icalendar2
2
+ module CalendarProperty
3
+ # See http://tools.ietf.org/html/rfc5545#section-3.7.2
4
+ class Method < Property::Base
5
+ name "METHOD"
6
+ value :types => [:text]
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module Icalendar2
2
+ module CalendarProperty
3
+ # See http://tools.ietf.org/html/rfc5545#section-3.7.3
4
+ class Prodid < Property::Base
5
+ name "PRODID"
6
+ value :types => [:text]
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module Icalendar2
2
+ module CalendarProperty
3
+ # See http://tools.ietf.org/html/rfc5545#section-3.7.4
4
+ class Version < Property::Base
5
+ name "VERSION"
6
+ value :types => [:text]
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,12 @@
1
+ module Icalendar2
2
+ module CalendarProperty
3
+ def self.get_factory(calendar_property_name)
4
+ # "VERSION" => CalendarProperty::Version, etc.
5
+ begin
6
+ const_get(calendar_property_name.capitalize, false)
7
+ rescue NameError
8
+ nil
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,90 @@
1
+ module Icalendar2
2
+ module Component
3
+ require 'socket'
4
+
5
+ class Base
6
+
7
+ class << self; attr_reader :property_names end
8
+ class << self; attr_reader :required_property_names end
9
+
10
+ def self.requires(properties = {})
11
+ add_properties(properties)
12
+ @required_property_names = properties.values.flatten
13
+ end
14
+
15
+ def self.accepts(properties = {})
16
+ add_properties(properties)
17
+ end
18
+
19
+ def initialize
20
+ @properties = {}
21
+ end
22
+
23
+ def new_uid
24
+ "#{DateTime.now}_#{rand(999999999)}@#{Socket.gethostname}"
25
+ end
26
+
27
+ def new_timestamp
28
+ DateTime.now
29
+ end
30
+
31
+ def valid?
32
+ present_property_names = @properties.keys
33
+ @properties.values.flatten.all?(&:valid?) &&
34
+ self.class.required_property_names.all? { |p| present_property_names.include?(p) }
35
+ end
36
+
37
+ def set_property(property_name, value, parameters = {})
38
+ name = (property_name == "class" ? "klass" : property_name)
39
+ unless self.class.property_names.include? name.to_sym
40
+ raise "#{self.class} component property #{name} not defined"
41
+ end
42
+ self.send(name, value, parameters)
43
+ end
44
+
45
+ private
46
+
47
+ def self.add_properties(properties)
48
+ single_properties = properties[:exactly_one] || []
49
+ single_properties.each do |property_sym|
50
+ define_accessor(property_sym)
51
+ end
52
+ multiple_properties = properties[:one_or_more] || []
53
+ multiple_properties.each do |property_sym|
54
+ define_multiple_accessor(property_sym)
55
+ end
56
+ @property_names ||= []
57
+ @property_names += single_properties + multiple_properties
58
+ end
59
+
60
+ def self.define_accessor(property_sym)
61
+ define_method(property_sym) do |*args|
62
+ value, parameters = *args
63
+ if value.nil?
64
+ @properties[property_sym]
65
+ else
66
+ @properties[property_sym] = Property.get_factory(property_sym).new(value, parameters)
67
+ end
68
+ end
69
+ alias_method "#{property_sym}=", property_sym
70
+ end
71
+
72
+ def self.define_multiple_accessor(property_sym)
73
+ property_factory = Property.get_factory(property_sym)
74
+ define_method(property_factory::PLURAL) do
75
+ @properties[property_sym]
76
+ end
77
+ define_method(property_sym) do |*args|
78
+ value, parameters = *args
79
+ @properties[property_sym] ||= []
80
+ @properties[property_sym] << property_factory.new(value, parameters)
81
+ end
82
+ alias_method "#{property_sym}=", property_sym
83
+ end
84
+
85
+ def properties_to_ical
86
+ @properties.values.flatten.map(&:to_ical).join
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,32 @@
1
+ module Icalendar2
2
+ # See http://tools.ietf.org/html/rfc5545#section-3.6.1
3
+ class Event < Component::Base
4
+ VALUE = "VEVENT"
5
+
6
+ requires :exactly_one => [:dtstamp, :uid]
7
+ accepts :exactly_one => [:dtstart, :klass, :created, :description, :geo,
8
+ :last_mod, :location, :organizer, :priority, :sequence, :status, :summary,
9
+ :transp, :url, :recurid, :dtend, :duration],
10
+ :one_or_more => [:rrule, :attach, :attendee, :categories, :comment,
11
+ :contact, :exdate, :rstatus, :related_to, :resources, :rdate]
12
+
13
+ def initialize
14
+ super
15
+ self.uid = new_uid
16
+ self.dtstamp = new_timestamp
17
+ end
18
+
19
+ def to_ical
20
+ str = "#{Tokens::COMPONENT_BEGIN}:#{VALUE}#{Tokens::CRLF}"
21
+ str << properties_to_ical
22
+ #str << alarm_to_ical
23
+ str << "#{Tokens::COMPONENT_END}:#{VALUE}#{Tokens::CRLF}"
24
+ end
25
+
26
+ private
27
+
28
+ def alarm_to_ical
29
+ #alarm.print if alarm
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,8 @@
1
+ module Icalendar2
2
+ module Component
3
+ def self.get_factory(component_name)
4
+ # "VFOO" => "Foo", etc.
5
+ Icalendar2.const_get(component_name.gsub(/^V/, "").capitalize, false)
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ module Icalendar2
2
+ module Parameter
3
+ # See http://tools.ietf.org/html/rfc5545#section-3.2.1
4
+ class Altrep < Base
5
+ format /^ALTREP="#{Tokens::URI}"$/
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,34 @@
1
+ module Icalendar2
2
+ module Parameter
3
+ class Base
4
+
5
+ class << self; attr_reader :format_matcher end
6
+
7
+ def self.format(matcher)
8
+ @format_matcher = matcher
9
+ end
10
+
11
+ def initialize(value)
12
+ @valid = true
13
+ @value = value
14
+ validate
15
+ end
16
+
17
+ def valid?
18
+ @valid
19
+ end
20
+
21
+ private
22
+
23
+ def validate
24
+ unless self.class.format_matcher.nil?
25
+ if @value.respond_to?(:to_s) && (val = @value.to_s).respond_to?(:match)
26
+ @valid = !!val.match(self.class.format_matcher)
27
+ else
28
+ @valid = false
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,8 @@
1
+ module Icalendar2
2
+ module Parameter
3
+ # See http://tools.ietf.org/html/rfc5545#section-3.2.2
4
+ class Cn < Base
5
+ format /^CN=#{Tokens::PARAM_VALUE}$/
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ module Icalendar2
2
+ module Parameter
3
+ # See http://tools.ietf.org/html/rfc5545#section-3.2.3
4
+ class Cutype < Base
5
+ format /^CUTYPE=(?:INDIVIDUAL|GROUP|RESOURCE|ROOM|UNKNOWN|(?:#{Tokens::X_NAME})|(?:#{Tokens::IANA_TOKEN}))$/
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ module Icalendar2
2
+ module Parameter
3
+ # See http://tools.ietf.org/html/rfc5545#section-3.2.4
4
+ class DelegatedFrom < Base
5
+ format /^DELEGATED-FROM="#{Tokens::URI}"(?:,"#{Tokens::URI}")*$/
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ module Icalendar2
2
+ module Parameter
3
+ # See http://tools.ietf.org/html/rfc5545#section-3.2.5
4
+ class DelegatedTo < Base
5
+ format /^DELEGATED-TO="#{Tokens::URI}"(?:,"#{Tokens::URI}")*$/
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ module Icalendar2
2
+ module Parameter
3
+ # See http://tools.ietf.org/html/rfc5545#section-3.2.6
4
+ class Dir < Base
5
+ format /^DIR="#{Tokens::URI}"$/
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ module Icalendar2
2
+ module Parameter
3
+ # See http://tools.ietf.org/html/rfc5545#section-3.2.7
4
+ class Encoding < Base
5
+ format /^ENCODING=(?:8BIT|BASE64)$/
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,10 @@
1
+ module Icalendar2
2
+ module Parameter
3
+ # See http://tools.ietf.org/html/rfc5545#section-3.2.8
4
+ class Fmttype < Base
5
+ TYPE_NAME = "(?:#{Tokens::ALPHA}|#{Tokens::DIGIT}|[-!#$&.+^_])+"
6
+ SUBTYPE_NAME = TYPE_NAME
7
+ format /^FMTTYPE=#{TYPE_NAME}\/#{SUBTYPE_NAME}$/
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,4 @@
1
+ module Icalendar2
2
+ module Parameter
3
+ end
4
+ end
@@ -0,0 +1,136 @@
1
+ module Icalendar2
2
+ require 'stringio'
3
+
4
+ class Parser
5
+
6
+ def initialize(source)
7
+ if source.respond_to?(:gets)
8
+ @file = source
9
+ elsif !source.nil? && source.respond_to?(:to_s)
10
+ @file = StringIO.new(source.to_s, 'r')
11
+ else
12
+ raise ArgumentError, "#{self.class}.new must be called with an IO-like object or a path"
13
+ end
14
+
15
+ @previous_line = next_line_of_file
16
+ end
17
+
18
+ # Returns an iCalendar stream (collection of iCalendar objects).
19
+ def parse
20
+ calendars = []
21
+
22
+ while (line = next_multiline)
23
+ fields = parse_line(line)
24
+ if fields[:name] == Tokens::COMPONENT_BEGIN && fields[:value] == Calendar::VALUE
25
+ calendar = parse_calendar
26
+ calendars << calendar
27
+ end
28
+ end
29
+
30
+ calendars
31
+ end
32
+
33
+ private
34
+
35
+ def parse_calendar
36
+ calendar = Calendar.new
37
+
38
+ while (line = next_multiline)
39
+ fields = parse_line(line)
40
+
41
+ name, value = fields[:name].upcase, fields[:value]
42
+ if name == Tokens::COMPONENT_BEGIN
43
+ component_factory = Component.get_factory(value)
44
+ if component_factory.nil?
45
+ raise "Unable to find component: #{value}"
46
+ end
47
+ calendar.add_component parse_component(component_factory.new)
48
+ else
49
+ calendar.set_property(name, value, fields[:params])
50
+ end
51
+ end
52
+
53
+ calendar
54
+ end
55
+
56
+ def parse_component(component)
57
+ while (line = next_multiline)
58
+ fields = parse_line(line)
59
+ name = fields[:name].downcase
60
+ break if name.upcase == Tokens::COMPONENT_END
61
+ params, value = fields[:params], fields[:value]
62
+ component.set_property(name, value, params)
63
+ end
64
+
65
+ component
66
+ end
67
+
68
+ CONTINUED_LINE = /^[ \t]/
69
+ NAME = "(?:#{Tokens::IANA_TOKEN}|#{Tokens::X_NAME})"
70
+ PARAM_NAME = "(?:#{Tokens::IANA_TOKEN}|#{Tokens::X_NAME})"
71
+ PARAM = "(?:#{PARAM_NAME}=#{Tokens::PARAM_VALUE}(?:,#{Tokens::PARAM_VALUE})*)"
72
+ PARAM_CAPTURE = "(#{PARAM_NAME})=(#{Tokens::PARAM_VALUE}(?:,#{Tokens::PARAM_VALUE})*)"
73
+ CONTENT_LINE = /^(#{NAME})(;#{PARAM})*:(#{Tokens::VALUE_CHAR}*)/
74
+
75
+ def next_line_of_file
76
+ line = @file.gets
77
+ line.chomp! unless line.nil?
78
+ end
79
+
80
+ def next_multiline
81
+ line = @previous_line
82
+
83
+ if line.nil?
84
+ return nil
85
+ end
86
+
87
+ loop do
88
+ next_line = next_line_of_file
89
+
90
+ if next_line =~ CONTINUED_LINE
91
+ line << next_line[1, next_line.size]
92
+ elsif next_line.nil? || next_line.size > 0
93
+ @previous_line = next_line
94
+ break
95
+ end
96
+ end
97
+
98
+ line
99
+ end
100
+
101
+ def parse_line(line)
102
+ unless line =~ %r{#{CONTENT_LINE}}i
103
+ raise "Invalid line in calendar string: #{line}"
104
+ end
105
+
106
+ name = $1.upcase
107
+ value = unescape($3)
108
+ params = parse_params_str($2)
109
+
110
+ { :name => name, :value => value, :params => params }
111
+ end
112
+
113
+ def unescape(str)
114
+ str.gsub("\\;", ";").gsub("\\,", ",").gsub("\\n", "\n").gsub("\\\\", "\\")
115
+ end
116
+
117
+ def parse_params_str(params_str)
118
+ params = {}
119
+ if !params_str.nil? && params_str.size > 1
120
+ params_str.scan(/#{PARAM_CAPTURE}/) do
121
+ name = $1
122
+ values = $2
123
+
124
+ params[name] ||= []
125
+ values.scan(/(#{Tokens::PARAM_VALUE})/) do
126
+ if $1.size > 0
127
+ params[name] << $1
128
+ end
129
+ end
130
+ end
131
+ end
132
+
133
+ params
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,10 @@
1
+ module Icalendar2
2
+ module Property
3
+ # See http://tools.ietf.org/html/rfc5545#section-3.8.1.1
4
+ class Attach < Base
5
+ PLURAL = "attachments"
6
+ name "ATTACH"
7
+ value :types => [:uri, :binary]
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,8 @@
1
+ module Icalendar2
2
+ module Property
3
+ # See http://tools.ietf.org/html/rfc5545#section-3.8.4.1
4
+ class Attendee < Base
5
+ PLURAL = "attendees"
6
+ end
7
+ end
8
+ end