em-zimbreasy 0.0.7

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.
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in em-zimbreasy.gemspec
4
+ gemspec
5
+ gem 'savon', :git => 'git://github.com/rankin/savon.git'
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Christopher Rankin
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,86 @@
1
+ # Em::Zimbreasy
2
+
3
+ 2007!
4
+
5
+ Crankin will get that joke, he is to be thanked for assistance on this.
6
+
7
+ This is an open sourced wrapper for the Zimbra API. I only added functionality for CRUD on Calender Appointments,
8
+ because that's all I needed for the job I'm getting done. However, the code already contained within the libraries for calendars
9
+ and the structure make this an easily extensible gem if you need it for other purposes.
10
+
11
+ The API documentation I am using is located at
12
+
13
+ http://files.zimbra.com/docs/soap_api/8.0/soapapi-zimbra-doc/api-reference/index.html
14
+
15
+ Everybody who's interested in finally wrapping Zimbra well for Ruby, please download the gem and submit pull requests
16
+ with more of the methods on this API built out(there are over 100.)
17
+
18
+ This gem is still in it's infancy and has little to no error handling! I appreciate all the help I can get on this project, Zimbra is big!
19
+
20
+ ## Installation
21
+
22
+ Add this line to your application's Gemfile:
23
+
24
+ gem 'em-zimbreasy'
25
+
26
+ And then execute:
27
+
28
+ $ bundle
29
+
30
+ Or install it yourself as:
31
+
32
+ $ gem install em-zimbreasy
33
+
34
+ ## Usage
35
+
36
+ It's pretty simple to use, I will post some examples:
37
+
38
+ zs = Em::Zimbreasy::Account.new('username', 'password', "https://yourzimbraserver.com/service/soap"); #login
39
+ zm = Em::Zimbreasy::Mail.new(zs); #create a Zimbreasy::Mail object. This has methods for the ZimbraMail submodule of their API.
40
+ z = zm.create_appointment({
41
+ :appointee_email => "neo@matrix.com",
42
+ :start_time => Time.now+1.days,
43
+ :end_time => (Time.now+1.days+1.hours),
44
+ :name => "Joss Whedon Meeting",
45
+ :subject => "Hallelujah",
46
+ :desc => "Ridiculous stuff happening here"
47
+ }) #I create an appointment.
48
+
49
+ => "BEGIN:VCALENDAR\r\nCALSCALE:GREGORIAN\r\nPRODID:iCalendar-Ruby\r\nVERSION:2.0\r\nBEGIN:VEVENT\r\nDESCRIPTION:Poopmaster\r\nDTEND:20130208T171612\r\nDTSTAMP:20130207T161614\r\nDTSTART:20130208T161612\r\nCLASS:PRIVATE\r\nSEQUENCE:0\r\nSUMMARY:Jossss\r\nUID:336-335\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"
50
+
51
+
52
+ create_appointment returns ics formatted data, for use with icalendar. Notice that the UID in the data is 336-335. This is an invitation id, not an appointment id.
53
+ The appointment id is 336, the first part of it.
54
+
55
+ The other methods of note:
56
+
57
+ zm.get_appointment(336) #takes appt id
58
+
59
+ zm.modify_appointment({
60
+ :appointee_email => "neo@matrix.com",
61
+ :start_time => Time.now,
62
+ :end_time => (Time.now+2.hours),
63
+ :name => "Joss Whedon!!!",
64
+ :subject => "Hallelujah 2: The Electric Boogaloo",
65
+ :desc => "Yoda Fights Back", :inv_id => "336-335"
66
+ })
67
+
68
+ zm.cancel_appointment("336-335")
69
+
70
+ You'll notice get_appointment uses an actual appointment id, whereas modify appt and cancel appt need inv ids. I don't know why this is,
71
+ it seems Zimbra API only works with invitation, not appointment ids, when it comes to these methods. Get just needs appt ids. The final method of note is
72
+
73
+ zm.get_appt_summaries(Time.now-1.days, Time.now)
74
+
75
+ This just returns appointment Ics texts in an array. First arg is a Time object representing start date, second arg is end date.
76
+
77
+ ## Contributing
78
+
79
+ It'd be great if someone could write tests for these methods, I haven't had the time. If you want to write tests,
80
+ please do!
81
+
82
+ 1. Fork it
83
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
84
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
85
+ 4. Push to the branch (`git push origin my-new-feature`)
86
+ 5. Create new Pull Request
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/em-zimbreasy/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Jordan Prince", "Christopher Rankin"]
6
+ gem.email = ["crankin@pangeaequity.com"]
7
+ gem.description = %q{A no-nonsense async gem for the nonsensical Zimbra API.}
8
+ gem.summary = %q{A no-nonsense async gem for the nonsensical Zimbra API.}
9
+ gem.homepage = ""
10
+
11
+ gem.files = `git ls-files`.split($\)
12
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
13
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
+ gem.name = "em-zimbreasy"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = Em::Zimbreasy::VERSION
17
+
18
+ gem.add_dependency 'test-unit'
19
+ gem.add_dependency 'em-http-request'
20
+ gem.add_dependency 'em-synchrony'
21
+ gem.add_dependency 'nokogiri'
22
+ gem.add_dependency 'icalendar'
23
+ end
@@ -0,0 +1,18 @@
1
+ require "icalendar"
2
+ require "exceptions/zimbreasy_timeout_exception"
3
+ require "em-zimbreasy/version"
4
+ require "em-zimbreasy/mail"
5
+ require "em-zimbreasy/account"
6
+ require "savon"
7
+
8
+ module Em
9
+ module Zimbreasy
10
+
11
+ #takes a Time object. outputs string for zimbra api calls.
12
+ def self.zimbra_date(time)
13
+ time.strftime("%Y%m%dT%H%M%S")
14
+ end
15
+
16
+
17
+ end
18
+ end
@@ -0,0 +1,68 @@
1
+ require 'timeout'
2
+
3
+ module Em
4
+ module Zimbreasy
5
+ class Account
6
+ attr_accessor :user, :pass, :endpoint, :client, :soap_namespace, :zimbra_namespace
7
+
8
+ def initialize(user, pass, endpoint, adapter)
9
+ HTTPI::Adapter.use = adapter
10
+ @user = user
11
+ @pass = pass
12
+ @endpoint = endpoint
13
+ @soap_namespace = {
14
+ "xmlns:soap" => "http://schemas.xmlsoap.org/soap/envelope/"
15
+ }
16
+ @zimbra_namespace = "urn:zimbraAccount"
17
+ auth_request
18
+ end
19
+
20
+ def auth_request
21
+ response = make_call(
22
+ :AuthRequest,
23
+ { :persistAuthTokenCookie => 1, :xmlns => @zimbra_namespace },
24
+ {
25
+ :account => @user,
26
+ :password => @pass,
27
+ :session => "",
28
+ :attributes! => { :account => { :by => "name" } }
29
+ }
30
+ )
31
+ @client = Savon.client(
32
+ namespace_identifier: :none,
33
+ pretty_print_xml: true,
34
+ log: false,
35
+ endpoint: @endpoint,
36
+ namespace: soap_namespace,
37
+ convert_request_keys_to: :none,
38
+ headers: { "Cookie" => response.http.headers["set-cookie"].split(";").first }
39
+ )
40
+ end
41
+
42
+ def make_call(method, attrs={}, message)
43
+ tries = 0
44
+ soap_namespace = @soap_namespace
45
+ #@soap_namespace is undefined inside client.request, is not this obj. So we define it here.
46
+
47
+ @client ||= Savon.client(
48
+ namespace_identifier: :none,
49
+ pretty_print_xml: true,
50
+ log: false,
51
+ endpoint: @endpoint,
52
+ namespace: soap_namespace,
53
+ convert_request_keys_to: :none
54
+ )
55
+ response = @client.call(method, attributes: attrs, message: message, soap_action: @zimbra_namespace)
56
+
57
+ rescue Timeout::Error => e
58
+ pp "Retrying"
59
+ tries+=1
60
+ if tries >= 4
61
+ throw ZimbreasyTimeoutException.new
62
+ else
63
+ retry
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,250 @@
1
+ module Em
2
+ module Zimbreasy
3
+ class Mail
4
+ include Icalendar
5
+ attr_accessor :account, :zimbra_namespace
6
+
7
+ def initialize(account)
8
+ @account = account
9
+ @zimbra_namespace = "urn:zimbraMail"
10
+ end
11
+
12
+ #Params can contain the following:
13
+ #:appointee_emails(req)
14
+ #:start_time(opt)
15
+ #:end_time(opt)
16
+ #:name(opt)
17
+ #:subject(opt)
18
+ #:desc(opt)
19
+ #:mime_type(opt)
20
+ #:is_org(opt) boolean is organizer or not
21
+ #:or -> organizer email
22
+ #Returns Appt's Inv id as UID in an i_cal text.. Is normally a number like 140-141,
23
+ #the first number is the appt id, which you need for getting.
24
+ def create_appointment(params)
25
+ params[:start_time] = Em::Zimbreasy.zimbra_date(params[:start_time]) if params[:start_time]
26
+ params[:end_time] = Em::Zimbreasy.zimbra_date(params[:end_time]) if params[:end_time]
27
+ response = account.make_call(
28
+ :CreateAppointmentRequest,
29
+ { "xmlns" => @zimbra_namespace, "echo" => (params[:echo] || "0")},
30
+ appointment_hash(params)
31
+ )
32
+ params.merge!({:appt_id => response.body[:create_appointment_response][:@inv_id]})
33
+
34
+ to_ical(params)
35
+ end
36
+
37
+ def get_free_busy(start_time, end_time, email)
38
+ start_time = start_time.to_i*1000 #it wants millis, to_i gives seconds.
39
+ end_time = end_time.to_i*1000
40
+
41
+ response = account.make_call(
42
+ :GetFreeBusyRequest,
43
+ {:xmlns => @zimbra_namespace, :s => start_time, :e => end_time},
44
+ { :usr => { :name => email }}
45
+ )
46
+
47
+ array = []
48
+ return response.body[:get_free_busy_response][:usr].reject { |k,v| k if k == :@id }.inject(Hash.new) do |hash, entry|
49
+ if entry[1].is_a?(Array)
50
+ array_of_times = entry[1].inject(Array.new) do |times_array, times_entry|
51
+ times_array << { :s => Time.at(times_entry[:@s].to_f/1000.0), :e => Time.at(times_entry[:@e].to_f/1000.0) }
52
+ times_array
53
+ end
54
+ hash[entry[0]] = array_of_times
55
+ else
56
+ hash[entry[0]] = [ {:s => Time.at(entry[1][:@s].to_f/1000.0), :e => Time.at(entry[1][:@e].to_f/1000.0)} ]
57
+ end
58
+ hash
59
+ end
60
+ end
61
+
62
+ def get_appointment(appt_id)
63
+
64
+ response = account.make_call(
65
+ :GetAppointmentRequest,
66
+ { :xmlns => @zimbra_namespace, :id => appt_id},
67
+ {}
68
+ )
69
+
70
+ comp = response.body[:get_appointment_response][:appt][:inv][:comp]
71
+
72
+ hash = {
73
+ :start_time => comp[:s][:@d],
74
+ :end_time => comp[:e][:@d],
75
+ :desc => comp[:desc],
76
+ :name => comp[:@name],
77
+ :appt_id => appt_id
78
+ }
79
+
80
+ to_ical(hash)
81
+ end
82
+
83
+ def get_appt_summaries(start_date, end_date)
84
+ start_date = start_date.to_i*1000 #it wants millis, to_i gives seconds.
85
+ end_date = end_date.to_i*1000
86
+
87
+ response = account.make_call(
88
+ :GetApptSummariesRequest,
89
+ { :xmlns => @zimbra_namespace, :e => end_date, :s => start_date},
90
+ {}
91
+ )
92
+
93
+ return [] if response.body[:get_appt_summaries_response][:appt].nil?
94
+
95
+ appts = []
96
+
97
+ if response.body[:get_appt_summaries_response][:appt].is_a?(Array)
98
+ response.body[:get_appt_summaries_response][:appt].each do |appt|
99
+
100
+ inst = appt[:inst]
101
+
102
+ hash = {
103
+ :start_time => Em::Zimbreasy.zimbra_date(Time.at(inst[:@s].to_f/1000.0)),
104
+ :name => appt[:@name],
105
+ :appt_id => appt[:@id]
106
+ }
107
+
108
+ appts << to_ical(hash)
109
+ end
110
+ else
111
+ appt = response.body[:get_appt_summaries_response][:appt]
112
+
113
+ inst = appt[:inst]
114
+
115
+ hash = {
116
+ :start_time => Zimbreasy.zimbra_date(Time.at(inst[:@s].to_f/1000.0)),
117
+ :name => appt[:@name],
118
+ :appt_id => appt[:@id]
119
+ }
120
+
121
+ appts << to_ical(hash)
122
+ end
123
+
124
+ appts
125
+ end
126
+
127
+ #same param options as create_appointment, but you can add :inv_id too.
128
+ def modify_appointment(params)
129
+ params[:start_time] = Em::Zimbreasy.zimbra_date(params[:start_time]) if params[:start_time]
130
+ params[:end_time] = Em::Zimbreasy.zimbra_date(params[:end_time]) if params[:end_time]
131
+
132
+ response = account.make_call(
133
+ :ModifyAppointmentRequest,
134
+ { :xmlns => @zimbra_namespace, :id => params[:inv_id]},
135
+ appointment_hash(params)
136
+ )
137
+
138
+ to_ical(params.merge({:appt_id => params[:inv_id]}))
139
+ end
140
+
141
+ #returns true if it worked, inv_id is not appt_id, it's normally something like 320-319, the first number is appt_id.
142
+ def cancel_appointment(inv_id, emails, subject=nil, content=nil)
143
+ unless inv_id and inv_id.is_a?(String) and inv_id.match(/-/) and inv_id.split("-").count==2 #so it has x-y formatting.
144
+ raise 'inv_id must be string of format x-y, where x and y are numbers.'
145
+ end
146
+
147
+ message = {
148
+ :m => {
149
+ :su => subject,
150
+ :mp => {
151
+ :content => content,
152
+ :@ct => "text/plain"
153
+ },
154
+ :attributes! => { :e => { :a => [], :t => [] } }
155
+ }
156
+ }
157
+
158
+ message[:m][:e] = []
159
+ emails.each do |email|
160
+ message[:m][:e] << ""
161
+ message[:m][:attributes!][:e][:a].push(email)
162
+ message[:m][:attributes!][:e][:t].push("t")
163
+ end
164
+
165
+ response = account.make_call(
166
+ :CancelAppointmentRequest,
167
+ { :xmlns => @zimbra_namespace, :id => inv_id, :comp => 0},
168
+ message
169
+ )
170
+
171
+ return !response.body[:cancel_appointment_response].nil?
172
+ end
173
+
174
+ private
175
+
176
+ def to_ical(params)
177
+ calendar = Calendar.new
178
+ calendar.event do
179
+ dtstart params[:start_time]
180
+ dtend params[:end_time]
181
+ summary params[:name]
182
+ description params[:desc]
183
+ uid params[:appt_id]
184
+ klass "PRIVATE"
185
+ end
186
+ calendar.to_ical
187
+ end
188
+
189
+ def appointment_hash(params)
190
+ message = {
191
+ :m => {
192
+ :mp => { :ct => (params[:mime_type] || "text/plain") },
193
+ :inv => {
194
+ :mp => { :ct =>(params[:mime_type] || "text/plain") },
195
+ :desc => params[:desc],
196
+ :comment => params[:desc],
197
+ :@rsvp => "0",
198
+ :@compNum => "0",
199
+ :@method => "none",
200
+ :@name => params[:name],
201
+ :@isOrg => params[:is_org],
202
+ :@noBlob => 1
203
+ },
204
+ :@su => params[:subject],
205
+ :attributes! => { :e => { :a => [], :t => [] } }
206
+ }
207
+ }
208
+
209
+ message[:m][:inv][:s] = { :d => params[:start_time], :tz => params[:tz] } if params[:start_time]
210
+ message[:m][:inv][:e] = { :d => params[:end_time], :tz => params[:tz] } if params[:end_time]
211
+ message[:m][:inv][:fb] = params[:fb] if params[:fb]
212
+ message[:m][:inv][:fba] = params[:fba] if params[:fba]
213
+
214
+ message[:m][:inv][:comp] = {
215
+ :s => { :d => params[:start_time], :tz => params[:tz] },
216
+ :desc => params[:desc],
217
+ :@noBlob => 1,
218
+ :@method => "none", :@compNum => 1, :@rsvp => "0", :@isOrg => params[:is_org],
219
+ :attributes! => { :at => { :a => [], :rsvp => [], :role => [], :ptst => [], :cutype => [] } }
220
+ }
221
+
222
+ message[:m][:inv][:comp][:@fb] = params[:fb] if params[:fb]
223
+ message[:m][:inv][:comp][:@fb] = params[:fba] if params[:fba]
224
+
225
+ message[:m][:inv][:comp][:e] = { :d => params[:end_time], :tz => params[:tz] }
226
+
227
+ message[:m][:inv][:comp][:or] = { :@a => params[:or] }
228
+ message[:m][:inv][:or] = { :@a => params[:or] } #set organizer
229
+
230
+ message[:m][:e] = []
231
+ message[:m][:inv][:comp][:at] = []
232
+ params[:appointee_emails].each do |email|
233
+
234
+ message[:m][:e] << ""
235
+ message[:m][:attributes!][:e][:a].push(email) #set attendees here
236
+ message[:m][:attributes!][:e][:t].push("t")
237
+
238
+ message[:m][:inv][:comp][:at] << ""
239
+ message[:m][:inv][:comp][:attributes!][:at][:a].push(email) # also set attendees here
240
+ message[:m][:inv][:comp][:attributes!][:at][:rsvp].push("1")
241
+ message[:m][:inv][:comp][:attributes!][:at][:role].push("REQ")
242
+ message[:m][:inv][:comp][:attributes!][:at][:ptst].push("AC")
243
+ message[:m][:inv][:comp][:attributes!][:at][:cutype].push("IND")
244
+ end
245
+ return message
246
+ end
247
+
248
+ end
249
+ end
250
+ end
@@ -0,0 +1,5 @@
1
+ module Em
2
+ module Zimbreasy
3
+ VERSION = "0.0.7"
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ class ZimbreasyTimeoutException < Exception
2
+ def message
3
+ 'Request timed out after multiple retries.'
4
+ end
5
+ end
metadata ADDED
@@ -0,0 +1,137 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: em-zimbreasy
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.7
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Jordan Prince
9
+ - Christopher Rankin
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2013-10-17 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: test-unit
17
+ requirement: !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: !ruby/object:Gem::Requirement
26
+ none: false
27
+ requirements:
28
+ - - ! '>='
29
+ - !ruby/object:Gem::Version
30
+ version: '0'
31
+ - !ruby/object:Gem::Dependency
32
+ name: em-http-request
33
+ requirement: !ruby/object:Gem::Requirement
34
+ none: false
35
+ requirements:
36
+ - - ! '>='
37
+ - !ruby/object:Gem::Version
38
+ version: '0'
39
+ type: :runtime
40
+ prerelease: false
41
+ version_requirements: !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ! '>='
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ - !ruby/object:Gem::Dependency
48
+ name: em-synchrony
49
+ requirement: !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ type: :runtime
56
+ prerelease: false
57
+ version_requirements: !ruby/object:Gem::Requirement
58
+ none: false
59
+ requirements:
60
+ - - ! '>='
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ - !ruby/object:Gem::Dependency
64
+ name: nokogiri
65
+ requirement: !ruby/object:Gem::Requirement
66
+ none: false
67
+ requirements:
68
+ - - ! '>='
69
+ - !ruby/object:Gem::Version
70
+ version: '0'
71
+ type: :runtime
72
+ prerelease: false
73
+ version_requirements: !ruby/object:Gem::Requirement
74
+ none: false
75
+ requirements:
76
+ - - ! '>='
77
+ - !ruby/object:Gem::Version
78
+ version: '0'
79
+ - !ruby/object:Gem::Dependency
80
+ name: icalendar
81
+ requirement: !ruby/object:Gem::Requirement
82
+ none: false
83
+ requirements:
84
+ - - ! '>='
85
+ - !ruby/object:Gem::Version
86
+ version: '0'
87
+ type: :runtime
88
+ prerelease: false
89
+ version_requirements: !ruby/object:Gem::Requirement
90
+ none: false
91
+ requirements:
92
+ - - ! '>='
93
+ - !ruby/object:Gem::Version
94
+ version: '0'
95
+ description: A no-nonsense async gem for the nonsensical Zimbra API.
96
+ email:
97
+ - crankin@pangeaequity.com
98
+ executables: []
99
+ extensions: []
100
+ extra_rdoc_files: []
101
+ files:
102
+ - .gitignore
103
+ - Gemfile
104
+ - LICENSE
105
+ - README.md
106
+ - Rakefile
107
+ - em-zimbreasy.gemspec
108
+ - lib/em-zimbreasy.rb
109
+ - lib/em-zimbreasy/account.rb
110
+ - lib/em-zimbreasy/mail.rb
111
+ - lib/em-zimbreasy/version.rb
112
+ - lib/exceptions/zimbreasy_timeout_exception.rb
113
+ homepage: ''
114
+ licenses: []
115
+ post_install_message:
116
+ rdoc_options: []
117
+ require_paths:
118
+ - lib
119
+ required_ruby_version: !ruby/object:Gem::Requirement
120
+ none: false
121
+ requirements:
122
+ - - ! '>='
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ required_rubygems_version: !ruby/object:Gem::Requirement
126
+ none: false
127
+ requirements:
128
+ - - ! '>='
129
+ - !ruby/object:Gem::Version
130
+ version: '0'
131
+ requirements: []
132
+ rubyforge_project:
133
+ rubygems_version: 1.8.23
134
+ signing_key:
135
+ specification_version: 3
136
+ summary: A no-nonsense async gem for the nonsensical Zimbra API.
137
+ test_files: []