caldav 0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,3 @@
1
+ CalDAV library
2
+
3
+ based on 4fthawaiian/ruby-caldav and collectiveidea/caldav
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'caldav'
4
+
5
+ prg = CalDAV::CalDAVer.new
6
+ prg.run_args( ARGV )
7
+
@@ -0,0 +1,99 @@
1
+ require './lib/caldav.rb'
2
+
3
+ class CalDAVTester
4
+
5
+ def initialize
6
+ $caldav = {
7
+ :host => 'mail.server.com',
8
+ :port => 443,
9
+ :url => '/caldav.php/martin.povolny@solnet.cz/test',
10
+ :user => 'user@test.com',
11
+ :password => 'yourpassword'
12
+ }
13
+ begin
14
+ require '~/.caldav_cred.rb'
15
+ rescue LoadError
16
+ end
17
+
18
+ @cal = CalDAV::Client.new $caldav[:host], $caldav[:port], $caldav[:url], $caldav[:user], $caldav[:password]
19
+ end
20
+
21
+ attr :cal
22
+
23
+ def test_create_event
24
+ myevent = Event.new
25
+ myevent.dtstart = DateTime.parse( '2012/08/11 09:45')
26
+ myevent.dtend = DateTime.parse( '2012/08/11 10:45')
27
+ myevent.summary = "Jo?"
28
+
29
+ uuid, response = cal.create myevent
30
+
31
+ puts "CREATE Response #{response.code} #{response.message}: #{response.body}"
32
+ puts "UUID: #{uuid}"
33
+
34
+ return uuid
35
+ end
36
+
37
+ def test_delete_event( uuid )
38
+ response = cal.delete uuid
39
+ puts "DETELE Response #{response.code} #{response.message}: #{response.body}"
40
+ end
41
+
42
+ def test_get_event( uuid )
43
+ ev, response = cal.get uuid
44
+ puts "GET Response #{response.code} #{response.message}: #{response.body}"
45
+ puts ev
46
+ end
47
+
48
+ def test_read_todo
49
+ puts '*' * 20 + ' TODO ' + '*' * 20
50
+ res = cal.todo
51
+
52
+ res.each{ |todo|
53
+ p todo
54
+ }
55
+ end
56
+
57
+ def test_report
58
+ puts '*' * 20 + ' EVENTS ' + '*' * 20
59
+ p cal.report "20111201T000000", "20131231T000000"
60
+ end
61
+
62
+ def test_query0
63
+ puts CalDAV::Query.event.to_xml
64
+
65
+ time1 = DateTime.parse('2011/08/08 09:45')
66
+ time2 = DateTime.parse('2012/08/08 10:45')
67
+ puts CalDAV::Query.event(time1..time2).to_xml
68
+ puts CalDAV::Query.event.uid( UUID.generate ).to_xml
69
+ #puts CalDAV::Query.todo.alarm(time1..time2).to_xml
70
+ #puts CalDAV::Query.event.attendee(email).partstat('NEEDS-ACTION').to_xml
71
+ #puts CalDAV::Query.todo.completed(false).status(:cancelled => false).to_xml
72
+ end
73
+
74
+ def test_query
75
+ p cal.query( CalDAV::Query.event ) #=> All events
76
+ p cal.query( CalDAV::Query.event(time1..time2) )
77
+ p cal.query( CalDAV::Query.event.uid("UID") )
78
+ p cal.query( CalDAV::Query.todo.alarm(time1..time2) )
79
+ p cal.query( CalDAV::Query.event.attendee(email).partstat('NEEDS-ACTION') )
80
+ p cal.query( CalDAV::Query.todo.completed(false).status(:cancelled => false) )
81
+ end
82
+ end
83
+
84
+ #r = CalDAV::Request::Report.new( "20111201T000000", "20131231T000000" )
85
+ #puts r.to_xml
86
+ #exit
87
+
88
+ t = CalDAVTester.new
89
+
90
+ t.test_query0
91
+
92
+ uuid = t.test_create_event
93
+ t.test_get_event( uuid )
94
+ t.test_delete_event( uuid )
95
+
96
+ t.test_report
97
+ t.test_read_todo
98
+ #t.test_query
99
+
@@ -0,0 +1,19 @@
1
+ require 'net/https'
2
+ require 'uuid'
3
+ require 'rexml/document'
4
+ require 'rexml/xpath'
5
+ require 'date'
6
+ require 'icalendar'
7
+ require 'time'
8
+
9
+ ['client.rb', 'request.rb', 'net.rb', 'query.rb', 'filter.rb', 'caldaver.rb'].each do |f|
10
+ require File.join( File.dirname(__FILE__), 'caldav', f )
11
+ end
12
+
13
+ class Event
14
+ attr_accessor :uid, :created, :dtstart, :dtend, :lastmodified, :summary, :description, :name, :action
15
+ end
16
+
17
+ class Todo
18
+ attr_accessor :uid, :created, :summary, :dtstart, :status, :completed
19
+ end
@@ -0,0 +1,47 @@
1
+ require 'optparse'
2
+
3
+ # caldav --user USER --password PASSWORD --uri URI --command COMMAND
4
+ # caldav --user martin@solnet.cz --password test --uri https://mail.solnet.cz/caldav.php/martin.povolny@solnet.cz/test --command create_event
5
+
6
+ module CalDAV
7
+
8
+ class CalDAVer
9
+ def run_args( args )
10
+ o = nil
11
+
12
+ help = Proc.new do
13
+ puts o.help
14
+ exit 1
15
+ end
16
+
17
+ options = {}
18
+ o = OptionParser.new do |o|
19
+ o.on('-p', '--password', String, 'Password') { |p| options[:password] = p }
20
+ o.on('-u', '--user', String, 'User (login)') { |l| options[:login] = l }
21
+ o.on('--uri [STRING]', String, 'Calendar URI') { |uri| options[:uri] = uri }
22
+ o.on('--command [STRING]', String, 'Command') { |c| options[:command] = command }
23
+ o.on('--begin [DATETIME]', DateTime, 'Start time') { |dt| options[:begin] = dt }
24
+ o.on('--end [DATETIME]', DateTime, 'End time') { |dt| options[:end] = dt }
25
+ o.on('-h') { help.call }
26
+ end
27
+
28
+ o.parse( args )
29
+
30
+ help.call if options[:command].to_s.empty? or options[:uri].to_s.empty?
31
+
32
+ cal = cal.new( options[:uri], options[:login], options[:password] )
33
+
34
+ case options[:command].to_s
35
+ when 'create'
36
+ when 'delete'
37
+ #when 'modify'
38
+ when 'get'
39
+ when 'report'
40
+ cal.report( options[:begin], options[:end] )
41
+ else
42
+ help.call
43
+ end
44
+ end
45
+ end
46
+
47
+ end
@@ -0,0 +1,243 @@
1
+ module CalDAV
2
+ class Client
3
+ include Icalendar
4
+ attr_accessor :host, :port, :url, :user, :password, :ssl
5
+
6
+ def initialize( *args )
7
+ case args.length
8
+ when 3
9
+ __init_from_uri( *args )
10
+ when 5
11
+ __init_from_host_port( *args )
12
+ else
13
+ raise "#{self.class.to_s}: invalid number of arguments: #{args.length}"
14
+ end
15
+ end
16
+
17
+ def __init_from_uri( suri, user, password )
18
+ uri = URI.new( suri )
19
+ puts "FIXME"
20
+ end
21
+
22
+ def __init_from_host_port( host, port, url, user, password )
23
+ @host = host
24
+ @port = port
25
+ @url = url
26
+ @user = user
27
+ @password = password
28
+ @ssl = port == 443
29
+ end
30
+
31
+ def __create_http
32
+ http = Net::HTTP.new(@host, @port)
33
+ http.use_ssl = @ssl
34
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
35
+ #http.set_debug_output $stderr
36
+ http
37
+ end
38
+
39
+ def report start, stop
40
+ res = nil
41
+ __create_http.start {|http|
42
+ req = Net::HTTP::Report.new(@url, initheader = {'Content-Type'=>'application/xml'} )
43
+ req.basic_auth @user, @password
44
+ req.body = CalDAV::Request::ReportVEVENT.new( start, stop ).to_xml
45
+ res = http.request( req )
46
+ }
47
+ result = []
48
+ xml = REXML::Document.new( res.body )
49
+ REXML::XPath.each( xml, '//c:calendar-data/', { "c"=>"urn:ietf:params:xml:ns:caldav"} ){ |c|
50
+ result += parse_events( c.text )
51
+ }
52
+ return result
53
+ end
54
+
55
+ def get uuid
56
+ res = nil
57
+ __create_http.start {|http|
58
+ req = Net::HTTP::Get.new("#{@url}/#{uuid}.ics")
59
+ req.basic_auth @user, @password
60
+ res = http.request( req )
61
+ }
62
+
63
+ # FIXME: parse event/todo/vcard
64
+ return parse_events( res.body ), res
65
+ end
66
+
67
+ def delete uuid
68
+ __create_http.start {|http|
69
+ req = Net::HTTP::Delete.new("#{@url}/#{uuid}.ics")
70
+ req.basic_auth @user, @password
71
+ res = http.request( req )
72
+ }
73
+ end
74
+
75
+ def create event
76
+ nowstr = DateTime.now.strftime "%Y%m%dT%H%M%SZ"
77
+ uuid = UUID.generate
78
+ dings = """BEGIN:VCALENDAR
79
+ PRODID:Caldav.rb
80
+ VERSION:2.0
81
+ BEGIN:VEVENT
82
+ CREATED:#{nowstr}
83
+ UID:#{uuid}
84
+ SUMMARY:#{event.summary}
85
+ DTSTART:#{event.dtstart.strftime("%Y%m%dT%H%M%S")}
86
+ DTEND:#{event.dtend.strftime("%Y%m%dT%H%M%S")}
87
+ END:VEVENT
88
+ END:VCALENDAR"""
89
+
90
+ res = nil
91
+ http = Net::HTTP.new(@host, @port)
92
+ __create_http.start { |http|
93
+ req = Net::HTTP::Put.new("#{@url}/#{uuid}.ics")
94
+ req['Content-Type'] = 'text/calendar'
95
+ req.basic_auth @user, @password
96
+ req.body = dings
97
+ res = http.request( req )
98
+ }
99
+ return uuid, res
100
+ end
101
+
102
+ def add_alarm tevent, altCal="Calendar"
103
+ #[#<Icalendar::Alarm:0x10b9d1b90 @name=\"VALARM\", @components={}, @properties={\"trigger\"=>\"-PT5M\", \"action\"=>\"DISPLAY\", \"description\"=>\"\"}>]
104
+ dtstart_string = ( Time.parse(tevent.dtstart.to_s) + Time.now.utc_offset.to_i.abs ).strftime "%Y%m%dT%H%M%S"
105
+ dtend_string = ( Time.parse(tevent.dtend.to_s) + Time.now.utc_offset.to_i.abs ).strftime "%Y%m%dT%H%M%S"
106
+ alarmText = <<EOL
107
+ BEGIN:VCALENDAR
108
+ VERSION:2.0
109
+ PRODID:Ruby iCalendar
110
+ BEGIN:VEVENT
111
+ UID:#{tevent.uid}
112
+ SUMMARY:#{tevent.summary}
113
+ DESCRIPTION:#{tevent.description}
114
+ DTSTART:#{dtstart_string}
115
+ DTEND:#{dtend_string}
116
+ BEGIN:VALARM
117
+ ACTION:DISPLAY
118
+ TRIGGER;RELATED=START:-PT5M
119
+ DESCRIPTION:Reminder
120
+ END:VALARM
121
+ BEGIN:VALARM
122
+ TRIGGER:-PT5M
123
+ ACTION:EMAIL
124
+ ATTENDEE:#{tevent.organizer}
125
+ SUMMARY:#{tevent.summary}
126
+ DESCRIPTION:#{tevent.description}
127
+ TRIGGER:-PT5M
128
+ END:VALARM
129
+ END:VEVENT
130
+ END:VCALENDAR
131
+ EOL
132
+ p alarmText
133
+ res = nil
134
+ puts "#{@url}/#{tevent.uid}.ics"
135
+ thttp = __create_http.start
136
+ #thttp.set_debug_output $stderr
137
+ req = Net::HTTP::Put.new("#{@url}/#{tevent.uid}.ics", initheader = {'Content-Type'=>'text/calendar'} )
138
+ req.basic_auth @user, @password
139
+ req.body = alarmText
140
+ res = thttp.request( req )
141
+ p res.inspect
142
+
143
+ return tevent.uid
144
+ end
145
+
146
+ def update event
147
+ dings = """BEGIN:VCALENDAR
148
+ PRODID:Caldav.rb
149
+ VERSION:2.0
150
+
151
+ BEGIN:VTIMEZONE
152
+ TZID:/Europe/Vienna
153
+ X-LIC-LOCATION:Europe/Vienna
154
+ BEGIN:DAYLIGHT
155
+ TZOFFSETFROM:+0100
156
+ TZOFFSETTO:+0200
157
+ TZNAME:CEST
158
+ DTSTART:19700329T020000
159
+ RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=-1SU;BYMONTH=3
160
+ END:DAYLIGHT
161
+ BEGIN:STANDARD
162
+ TZOFFSETFROM:+0200
163
+ TZOFFSETTO:+0100
164
+ TZNAME:CET
165
+ DTSTART:19701025T030000
166
+ RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=-1SU;BYMONTH=10
167
+ END:STANDARD
168
+ END:VTIMEZONE
169
+
170
+ BEGIN:VEVENT
171
+ CREATED:#{event.created}
172
+ UID:#{event.uid}
173
+ SUMMARY:#{event.summary}
174
+ DTSTART;TZID=Europe/Vienna:#{event.dtstart}
175
+ DTEND;TZID=Europe/Vienna:#{event.dtend.rfc3339}
176
+ END:VEVENT
177
+ END:VCALENDAR"""
178
+
179
+ res = nil
180
+ __create_http.start {|http|
181
+ req = Net::HTTP::Put.new("#{@url}/#{event.uid}.ics", initheader = {'Content-Type'=>'text/calendar'} )
182
+ req.basic_auth @user, @passowrd
183
+ req.body = dings
184
+ res = http.request( req )
185
+ }
186
+ return event.uid
187
+ end
188
+
189
+ def todo
190
+ res = nil
191
+ __create_http.start {|http|
192
+ req = Net::HTTP::Report.new(@url, initheader = {'Content-Type'=>'application/xml'} )
193
+ req.basic_auth @user, @password
194
+ req.body = CalDAV::Request::ReportVTODO.new.to_xml
195
+ res = http.request( req )
196
+ }
197
+ result = []
198
+ xml = REXML::Document.new( res.body )
199
+ #p res.body
200
+ REXML::XPath.each( xml, '//c:calendar-data/', { "c"=>"urn:ietf:params:xml:ns:caldav"} ){ |c|
201
+ p c.text
202
+ p parse_tasks( c.text )
203
+ result += parse_tasks( c.text )
204
+ }
205
+ return result
206
+ end
207
+
208
+ def parse_tasks( vcal )
209
+ return_tasks = Array.new
210
+ cals = Icalendar.parse(vcal)
211
+ cals.each { |tcal|
212
+ tcal.todos.each { |ttask| # FIXME
213
+ return_tasks << ttask
214
+ }
215
+ }
216
+ return return_tasks
217
+ end
218
+
219
+ def parse_events( vcal )
220
+ return_events = Array.new
221
+ cals = Icalendar.parse(vcal)
222
+ cals.each { |tcal|
223
+ tcal.events.each { |tevent|
224
+ if tevent.recurrence_id.to_s.empty? # skip recurring events
225
+ return_events << tevent
226
+ end
227
+ }
228
+ }
229
+ return return_events
230
+ end
231
+
232
+ def filterTimezone( vcal )
233
+ data = ""
234
+ inTZ = false
235
+ vcal.split("\n").each{ |l|
236
+ inTZ = true if l.index("BEGIN:VTIMEZONE")
237
+ data << l+"\n" unless inTZ
238
+ inTZ = false if l.index("END:VTIMEZONE")
239
+ }
240
+ return data
241
+ end
242
+ end
243
+ end
@@ -0,0 +1,78 @@
1
+ module CalDAV
2
+ module Filter
3
+ class Base
4
+ attr_accessor :parent, :child
5
+
6
+ def to_xml(xml = Builder::XmlMarkup.new(:indent => 2))
7
+ if parent
8
+ parent.to_xml
9
+ else
10
+ build_xml(xml)
11
+ end
12
+ end
13
+
14
+ def build_xml(xml)
15
+ #do nothing
16
+ end
17
+
18
+ def child=(child)
19
+ @child = child
20
+ child.parent = self
21
+ end
22
+ end
23
+
24
+ class Component < Base
25
+ attr_accessor :name
26
+
27
+ def initialize(name, parent = nil)
28
+ self.name = name
29
+ self.parent = parent
30
+ end
31
+
32
+ def time_range(range)
33
+ self.child = TimeRange.new(range, self)
34
+ end
35
+
36
+ def uid(uid)
37
+ self.child = Property.new("UID", uid, self)
38
+ end
39
+
40
+ def build_xml(xml)
41
+ xml.tag! "cal:comp-filter", :name => name do
42
+ child.build_xml(xml) unless child.nil?
43
+ end
44
+ end
45
+ end
46
+
47
+ class TimeRange < Base
48
+ attr_accessor :range
49
+
50
+ def initialize(range, parent = nil)
51
+ self.range = range
52
+ self.parent = parent
53
+ end
54
+
55
+ def build_xml(xml)
56
+ xml.tag! "cal:time-range",
57
+ :start => range.begin.to_ical,
58
+ :end => range.end.to_ical
59
+ end
60
+ end
61
+
62
+ class Property < Base
63
+ attr_accessor :name, :text
64
+
65
+ def initialize(name, text, parent = nil)
66
+ self.name = name
67
+ self.text = text
68
+ self.parent = parent
69
+ end
70
+
71
+ def build_xml(xml)
72
+ xml.tag! "cal:prop-filter", :name => self.name do
73
+ xml.tag! "cal:text-match", self.text, :collation => "i;octet"
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,16 @@
1
+ module Net
2
+ class HTTP
3
+ class Report < HTTPRequest
4
+ METHOD = 'REPORT'
5
+ REQUEST_HAS_BODY = true
6
+ RESPONSE_HAS_BODY = true
7
+ end
8
+
9
+ class Mkcalendar < HTTPRequest
10
+ METHOD = 'MKCALENDAR'
11
+ REQUEST_HAS_BODY = true
12
+ RESPONSE_HAS_BODY = true
13
+ end
14
+ end
15
+ end
16
+
@@ -0,0 +1,51 @@
1
+ module CalDAV
2
+ class Query
3
+ attr_accessor :child
4
+
5
+ #TODO: raise error if to_xml is called before child is assigned
6
+ def to_xml(xml = Builder::XmlMarkup.new(:indent => 2))
7
+ xml.instruct!
8
+ xml.tag! "cal:calendar-query", CalDAV::NAMESPACES do
9
+ xml.tag! "dav:prop" do
10
+ xml.tag! "dav:getetag"
11
+ xml.tag! "cal:calendar-data"
12
+ end
13
+ xml.tag! "cal:filter" do
14
+ cal = Filter::Component.new("VCALENDAR", self)
15
+ cal.child = self.child
16
+ cal.build_xml(xml)
17
+ end
18
+ end
19
+ end
20
+
21
+ def event(param = nil)
22
+ self.child = Filter::Component.new("VEVENT")
23
+ if param.is_a? Range
24
+ self.child.time_range(param)
25
+ elsif param.is_a? String
26
+ self.child.uid(param)
27
+ else
28
+ self.child
29
+ end
30
+ end
31
+
32
+ def todo(param = nil)
33
+ self.child = Filter::Component.new("VTODO")
34
+ self.child
35
+ end
36
+
37
+ def child=(child)
38
+ child.parent = self
39
+ @child = child
40
+ end
41
+
42
+ def self.event( param=nil )
43
+ self.new.event( param )
44
+ end
45
+
46
+
47
+ def self.todo( param=nil )
48
+ self.new.todo
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,76 @@
1
+ require 'builder'
2
+
3
+ module CalDAV
4
+ NAMESPACES = { "xmlns:d" => 'DAV:', "xmlns:c" => "urn:ietf:params:xml:ns:caldav" }
5
+ module Request
6
+ class Base
7
+ def initialize
8
+ @xml = Builder::XmlMarkup.new(:indent => 2)
9
+ @xml.instruct!
10
+ end
11
+ attr :xml
12
+ end
13
+
14
+ class Mkcalendar < Base
15
+ attr_accessor :displayname, :description
16
+
17
+ def initialize(displayname = nil, description = nil)
18
+ @displayname = displayname
19
+ @description = description
20
+ end
21
+
22
+ def to_xml
23
+ xml.c :mkcalendar, NAMESPACES do
24
+ xml.d :set do
25
+ xml.d :prop do
26
+ xml.d :displayname, displayname unless displayname.to_s.empty?
27
+ xml.tag! "c:calendar-description", description, "xml:lang" => "en" unless description.to_s.empty?
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+
34
+ class ReportVEVENT < Base
35
+ attr_accessor :tstart, :tend
36
+
37
+ def initialize( tstart=nil, tend=nil )
38
+ @tstart = tstart
39
+ @tend = tend
40
+ super()
41
+ end
42
+
43
+ def to_xml
44
+ xml.c 'calendar-query'.intern, NAMESPACES do
45
+ xml.d :prop do
46
+ xml.d :getetag
47
+ xml.c 'calendar-data'.intern
48
+ end
49
+ xml.c :filter do
50
+ xml.c 'comp-filter'.intern, :name=> 'VCALENDAR' do
51
+ xml.c 'comp-filter'.intern, :name=> 'VEVENT' do
52
+ xml.c 'time-range'.intern, :start=> "#{tstart}Z", :end=> "#{tend}Z"
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+
60
+ class ReportVTODO < Base
61
+ def to_xml
62
+ xml.c 'calendar-query'.intern, NAMESPACES do
63
+ xml.d :prop do
64
+ xml.d :getetag
65
+ xml.c 'calendar-data'.intern
66
+ end
67
+ xml.c :filter do
68
+ xml.c 'comp-filter'.intern, :name=> 'VCALENDAR' do
69
+ xml.c 'comp-filter'.intern, :name=> 'VTODO'
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,3 @@
1
+ module CalDAV
2
+ VERSION=0.1
3
+ end
@@ -0,0 +1,20 @@
1
+ require File.expand_path('../lib/caldav/version', __FILE__)
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = "caldav"
5
+ s.version = CalDAV::VERSION
6
+ s.summary = "Ruby CalDAV client"
7
+ s.description = "Ruby client for CalDAV calendar and tasks."
8
+ s.homepage = "https://github.com/martinpovolny/ruby-caldav"
9
+ s.authors = ["Martin Povolny"]
10
+ s.email = ["martin.povolny@gmail.com"]
11
+
12
+ s.add_runtime_dependency 'rexml'
13
+ s.add_runtime_dependency 'icalendar'
14
+ s.add_runtime_dependency 'uuid'
15
+ s.add_runtime_dependency 'builder'
16
+
17
+ s.files = `git ls-files`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{|f| File.basename(f)}
19
+ s.require_paths = ["lib"]
20
+ end
metadata ADDED
@@ -0,0 +1,104 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: caldav
3
+ version: !ruby/object:Gem::Version
4
+ version: '0.1'
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Martin Povolny
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-08-08 00:00:00.000000000 +02:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: rexml
17
+ requirement: &19375540 !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ! '>='
21
+ - !ruby/object:Gem::Version
22
+ version: '0'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: *19375540
26
+ - !ruby/object:Gem::Dependency
27
+ name: icalendar
28
+ requirement: &19375120 !ruby/object:Gem::Requirement
29
+ none: false
30
+ requirements:
31
+ - - ! '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: *19375120
37
+ - !ruby/object:Gem::Dependency
38
+ name: uuid
39
+ requirement: &19374700 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ! '>='
43
+ - !ruby/object:Gem::Version
44
+ version: '0'
45
+ type: :runtime
46
+ prerelease: false
47
+ version_requirements: *19374700
48
+ - !ruby/object:Gem::Dependency
49
+ name: builder
50
+ requirement: &19374280 !ruby/object:Gem::Requirement
51
+ none: false
52
+ requirements:
53
+ - - ! '>='
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ type: :runtime
57
+ prerelease: false
58
+ version_requirements: *19374280
59
+ description: Ruby client for CalDAV calendar and tasks.
60
+ email:
61
+ - martin.povolny@gmail.com
62
+ executables:
63
+ - caldav
64
+ extensions: []
65
+ extra_rdoc_files: []
66
+ files:
67
+ - README.rdoc
68
+ - bin/caldav
69
+ - caldavtest.rb
70
+ - lib/caldav.rb
71
+ - lib/caldav/caldaver.rb
72
+ - lib/caldav/client.rb
73
+ - lib/caldav/filter.rb
74
+ - lib/caldav/net.rb
75
+ - lib/caldav/query.rb
76
+ - lib/caldav/request.rb
77
+ - lib/caldav/version.rb
78
+ - ruby-caldav.gemspec
79
+ has_rdoc: true
80
+ homepage: https://github.com/martinpovolny/ruby-caldav
81
+ licenses: []
82
+ post_install_message:
83
+ rdoc_options: []
84
+ require_paths:
85
+ - lib
86
+ required_ruby_version: !ruby/object:Gem::Requirement
87
+ none: false
88
+ requirements:
89
+ - - ! '>='
90
+ - !ruby/object:Gem::Version
91
+ version: '0'
92
+ required_rubygems_version: !ruby/object:Gem::Requirement
93
+ none: false
94
+ requirements:
95
+ - - ! '>='
96
+ - !ruby/object:Gem::Version
97
+ version: '0'
98
+ requirements: []
99
+ rubyforge_project:
100
+ rubygems_version: 1.6.2
101
+ signing_key:
102
+ specification_version: 3
103
+ summary: Ruby CalDAV client
104
+ test_files: []