rews 0.1.0

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/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Trampoline Systems Ltd
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,52 @@
1
+ = rews
2
+
3
+ Rews is a simple Ruby client for Exchange Web Services.
4
+
5
+ * Searching, fetching and deletion are supported.
6
+ * Filtering, ordering, and arbitrary property retrieval are supported.
7
+ * Bulk operations are supported
8
+
9
+ Method names generally follow the Exchange Web Services API
10
+ http://msdn.microsoft.com/en-us/library/bb409286(EXCHG.140).aspx
11
+
12
+ to use :
13
+
14
+ # create a client
15
+ c = Rews::Client.new("https://exchange.bar.com/EWS/Exchange.asmx", :ntlm, 'EXCHDOM\foo', 'password')
16
+
17
+ # find a distinguished folder from any one of the mailboxes the user has permissions for
18
+ inbox=c.distinguished_folder_id('inbox', 'foo@bar.com')
19
+
20
+ # find some message_ids,
21
+ mids = inbox.find_item_id(:restriction=>[:<=, "item:DateTimeReceived",DateTime.now],
22
+ :sort_order=>[["item:DateTimeReceived", "Ascending"]],
23
+ :indexed_page_item_view=>{:max_entries_returned=>10, :offset=>0})
24
+
25
+ # get some properties for a bunch of messages in one hit
26
+ messages = inbox.get_item(mids, :item_shape=>{
27
+ :base_shape=>:IdOnly,
28
+ :additional_properties=>[
29
+ [:field_uri, "item:Subject"],
30
+ [:field_uri, "item:DateTimeReceived"],
31
+ [:field_uri, "message:InternetMessageId"]]})
32
+
33
+ # delete the items to the DeletedItems folder
34
+ inbox.delete_item(mids, :delete_type=>:MoveToDeletedItems)
35
+
36
+ == Install
37
+
38
+ gem install rews
39
+
40
+ == Note on Patches/Pull Requests
41
+
42
+ * Fork the project.
43
+ * Make your feature addition or bug fix.
44
+ * Add tests for it. This is important so I don't break it in a
45
+ future version unintentionally.
46
+ * Commit, do not mess with rakefile, version, or history.
47
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
48
+ * Send me a pull request. Bonus points for topic branches.
49
+
50
+ == Copyright
51
+
52
+ Copyright (c) 2011 Trampoline Systems Ltd. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,50 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "rews"
8
+ gem.summary = %Q{a Ruby client for Exchange Web Services}
9
+ gem.description = %Q{an email focussed Ruby client for Exchange Web Services atop Savon}
10
+ gem.email = "craig@trampolinesystems.com"
11
+ gem.homepage = "http://github.com/trampoline/rews"
12
+ gem.authors = ["Trampoline Systems Ltd"]
13
+ gem.add_dependency "savon", ">= 0.8.6"
14
+ gem.add_dependency "ntlm-http", ">= 0.1.1"
15
+ gem.add_dependency "httpclient", ">= 2.1.6.1.1"
16
+ gem.add_dependency "fetch_in", ">= 0.2.0"
17
+ gem.add_development_dependency "rspec", ">= 1.2.9"
18
+ gem.add_development_dependency "rr", ">= 0.10.5"
19
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
20
+ end
21
+ Jeweler::GemcutterTasks.new
22
+ rescue LoadError
23
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
24
+ end
25
+
26
+ require 'spec/rake/spectask'
27
+ Spec::Rake::SpecTask.new(:spec) do |spec|
28
+ spec.libs << 'lib' << 'spec'
29
+ spec.spec_files = FileList['spec/**/*_spec.rb']
30
+ end
31
+
32
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
33
+ spec.libs << 'lib' << 'spec'
34
+ spec.pattern = 'spec/**/*_spec.rb'
35
+ spec.rcov = true
36
+ end
37
+
38
+ task :spec => :check_dependencies
39
+
40
+ task :default => :spec
41
+
42
+ require 'rake/rdoctask'
43
+ Rake::RDocTask.new do |rdoc|
44
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
45
+
46
+ rdoc.rdoc_dir = 'rdoc'
47
+ rdoc.title = "rews #{version}"
48
+ rdoc.rdoc_files.include('README*')
49
+ rdoc.rdoc_files.include('lib/**/*.rb')
50
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,274 @@
1
+ module Rews
2
+ module Folder
3
+ class Folder
4
+ attr_reader :client
5
+ attr_reader :folder_id
6
+ attr_reader :attributes
7
+
8
+ def initialize(client, folder)
9
+ @folder_id = VanillaFolderId.new(client, folder[:folder_id])
10
+ @attributes = folder
11
+ end
12
+
13
+ def [](key)
14
+ @attributes[key]
15
+ end
16
+
17
+ def keys
18
+ @attributes.keys
19
+ end
20
+ end
21
+
22
+ class FindResult
23
+ VIEW_ATTRS = [:includes_last_item_in_range,
24
+ :indexed_paging_offset,
25
+ :total_items_in_view]
26
+
27
+ VIEW_ATTRS.each do |attr|
28
+ attr_reader attr
29
+ end
30
+ attr_reader :result
31
+
32
+ def initialize(view, &proc)
33
+ VIEW_ATTRS.each do |attr|
34
+ self.instance_variable_set("@#{attr}", view[attr])
35
+ end
36
+ @result = proc.call(view) if proc
37
+ end
38
+
39
+ def length
40
+ result.length
41
+ end
42
+
43
+ def size
44
+ result.size
45
+ end
46
+
47
+ def [](key)
48
+ result[key]
49
+ end
50
+ end
51
+
52
+ class BaseFolderId
53
+ include Util
54
+
55
+ attr_reader :client
56
+
57
+ def initialize(client)
58
+ @client=client
59
+ end
60
+
61
+ FIND_FOLDER_OPTS = {
62
+ :restriction=>nil,
63
+ :indexed_page_folder_view=>View::INDEXED_PAGE_VIEW_OPTS,
64
+ :folder_shape=>Shape::FOLDER_SHAPE_OPTS}
65
+
66
+ def find_folder(opts={})
67
+ opts = check_opts(FIND_FOLDER_OPTS, opts)
68
+
69
+ r = with_error_check(client, :find_folder_response, :response_messages, :find_folder_response_message) do
70
+ client.request(:wsdl, "FindFolder", "Traversal"=>"Shallow") do
71
+ soap.namespaces["xmlns:t"]=SCHEMA_TYPES
72
+ xml = Builder::XmlMarkup.new
73
+
74
+ xml << Shape::FolderShape.new(opts[:folder_shape]||{}).to_xml
75
+ xml << View::IndexedPageFolderView.new(opts[:indexed_page_folder_view]).to_xml if opts[:indexed_page_folder_view]
76
+ xml << Restriction.new(opts[:restriction]).to_xml if opts[:restriction]
77
+
78
+ xml.wsdl :ParentFolderIds do
79
+ xml << Gyoku.xml(self.to_xml_hash)
80
+ end
81
+ soap.body = xml.target!
82
+ end
83
+ end
84
+
85
+ FindResult.new(r.fetch_in(:root_folder)) do |view|
86
+ results = view.fetch_in(:folders, :folder)
87
+ results = [results] if !results.is_a?(Array)
88
+ results.compact.map do |folder|
89
+ Folder.new(client, folder)
90
+ end
91
+ end
92
+ end
93
+
94
+ def find_folder_id(opts={})
95
+ opts = check_opts(FIND_FOLDER_OPTS, opts)
96
+
97
+ shape = opts[:folder_shape] ||={}
98
+ shape[:base_shape]||=:IdOnly
99
+
100
+ r = find_folder(opts)
101
+ r.result.map!(&:folder_id)
102
+ r
103
+ end
104
+
105
+ FIND_ITEM_OPTS = {
106
+ :restriction=>nil,
107
+ :sort_order=>nil,
108
+ :indexed_page_item_view=>View::INDEXED_PAGE_VIEW_OPTS,
109
+ :item_shape=>Shape::ITEM_SHAPE_OPTS}
110
+
111
+ # find message-ids in a folder
112
+ def find_item(opts={})
113
+ opts = check_opts(FIND_ITEM_OPTS, opts)
114
+
115
+ r = with_error_check(client, :find_item_response, :response_messages, :find_item_response_message) do
116
+ client.request(:wsdl, "FindItem", "Traversal"=>"Shallow") do
117
+ soap.namespaces["xmlns:t"]=SCHEMA_TYPES
118
+
119
+ xml = Builder::XmlMarkup.new
120
+
121
+ xml << Shape::ItemShape.new(opts[:item_shape]||{}).to_xml
122
+ xml << View::IndexedPageItemView.new(opts[:indexed_page_item_view]).to_xml if opts[:indexed_page_item_view]
123
+ xml << Restriction.new(opts[:restriction]).to_xml if opts[:restriction]
124
+ xml << SortOrder.new(opts[:sort_order]).to_xml if opts[:sort_order]
125
+
126
+ xml.wsdl :ParentFolderIds do
127
+ xml << Gyoku.xml(self.to_xml_hash)
128
+ end
129
+
130
+ soap.body = xml.target!
131
+ end
132
+ end
133
+
134
+ FindResult.new(r.to_hash.fetch_in(:root_folder)) do |view|
135
+ results = Item.read_items(client, view[:items])
136
+ end
137
+ end
138
+
139
+ def find_item_id(opts={})
140
+ opts = check_opts(FIND_ITEM_OPTS, opts)
141
+
142
+ shape = opts[:item_shape] ||= {}
143
+ shape[:base_shape]||=:IdOnly
144
+
145
+ r = find_item(opts)
146
+ r.result.map!(&:item_id)
147
+ r
148
+ end
149
+
150
+ GET_ITEM_OPTS = {
151
+ :item_shape=>Shape::ITEM_SHAPE_OPTS,
152
+ :ignore_change_keys=>nil
153
+ }
154
+
155
+ # get a bunch of messages in one api hit
156
+ def get_item(message_ids, opts={})
157
+ opts = check_opts(GET_ITEM_OPTS, opts)
158
+ message_ids = message_ids.result if message_ids.is_a?(FindResult)
159
+
160
+ r = with_error_check(client, :get_item_response,:response_messages,:get_item_response_message) do
161
+ client.request(:wsdl, "GetItem") do
162
+ soap.namespaces["xmlns:t"]=SCHEMA_TYPES
163
+
164
+ xml = Builder::XmlMarkup.new
165
+
166
+ xml << Shape::ItemShape.new(opts[:item_shape]||{}).to_xml
167
+ xml.wsdl :ItemIds do
168
+ message_ids.each do |mid|
169
+ xml << Gyoku.xml(mid.to_xml_hash(opts[:ignore_change_keys]))
170
+ end
171
+ end
172
+
173
+ soap.body = xml.target!
174
+ end
175
+ end
176
+ Item.read_get_item_response_messages(client, r)
177
+ end
178
+
179
+ DELETE_ITEM_OPTS = {
180
+ :delete_type! =>nil,
181
+ :ignore_change_keys=>false
182
+ }
183
+
184
+ def delete_item(message_ids, opts={})
185
+ opts = check_opts(DELETE_ITEM_OPTS, opts)
186
+ message_ids = message_ids.result if message_ids.is_a?(FindResult)
187
+
188
+ r = with_error_check(client, :delete_item_response, :response_messages, :delete_item_response_message) do
189
+ client.request(:wsdl, "DeleteItem", :DeleteType=>opts[:delete_type]) do
190
+ soap.namespaces["xmlns:t"]=SCHEMA_TYPES
191
+
192
+ xml = Builder::XmlMarkup.new
193
+
194
+ xml.wsdl :ItemIds do
195
+ message_ids.each do |mid|
196
+ xml << Gyoku.xml(mid.to_xml_hash(opts[:ignore_change_keys]))
197
+ end
198
+ end
199
+
200
+ soap.body = xml.target!
201
+ end
202
+ end
203
+ true
204
+ end
205
+ end
206
+
207
+ class VanillaFolderId < BaseFolderId
208
+ attr_reader :id
209
+ attr_reader :change_key
210
+
211
+ def initialize(client, folder_id)
212
+ super(client)
213
+ @id=folder_id[:id]
214
+ @change_key=folder_id[:change_key]
215
+ raise "no id" if !@id
216
+ end
217
+
218
+ def to_xml_hash
219
+ if change_key
220
+ {
221
+ "t:FolderId"=>"",
222
+ :attributes! => {
223
+ "t:FolderId" => {
224
+ "Id" => id.to_s,
225
+ "ChangeKey" => change_key.to_s}}}
226
+ else
227
+ {
228
+ "t:FolderId"=>"",
229
+ :attributes! => {
230
+ "t:FolderId" => {
231
+ "Id" => id.to_s}}}
232
+ end
233
+ end
234
+
235
+ def inspect
236
+ "#{self.class}(id: #{id}, change_key: #{change_key})"
237
+ end
238
+ end
239
+
240
+ class DistinguishedFolderId < BaseFolderId
241
+ attr_reader :id
242
+ attr_reader :mailbox_email
243
+
244
+ def initialize(client, id, mailbox_email=nil)
245
+ super(client)
246
+ @id = id
247
+ @mailbox_email = mailbox_email
248
+ raise "no id" if !@id
249
+ end
250
+
251
+ def to_xml_hash
252
+ {
253
+ "t:DistinguishedFolderId"=>mailbox_xml_hash,
254
+ :attributes! => {"t:DistinguishedFolderId"=>{"Id"=>id}}}
255
+ end
256
+
257
+ def inspect
258
+ "#{self.class}(id: #{id}, mailbox_email: #{mailbox_email})"
259
+ end
260
+
261
+ private
262
+
263
+ def mailbox_xml_hash
264
+ if mailbox_email
265
+ {
266
+ "t:Mailbox"=>{
267
+ "t:EmailAddress"=>mailbox_email}}
268
+ else
269
+ ""
270
+ end
271
+ end
272
+ end
273
+ end
274
+ end
data/lib/rews/item.rb ADDED
@@ -0,0 +1,127 @@
1
+ module Rews
2
+ module Item
3
+ module_function
4
+
5
+ # return a list of Item objects given a hash formed from an Items element
6
+ def read_items(client, items)
7
+ items.map do |item_class,items_of_class|
8
+ items_of_class = [items_of_class] if !items_of_class.is_a?(Array)
9
+ items_of_class.map do |item|
10
+ Item.new(client, item_class, item)
11
+ end
12
+ end.flatten
13
+ end
14
+
15
+ # return a list of Item objects from a list of GetItemResponseMessages
16
+ def read_get_item_response_messages(client, get_item_response_messages)
17
+ get_item_response_messages = [get_item_response_messages] if !get_item_response_messages.is_a?(Array)
18
+ items = get_item_response_messages.map do |girm|
19
+ read_items(client, girm[:items])
20
+ end.flatten
21
+ end
22
+
23
+ class Item
24
+ attr_reader :client
25
+ attr_reader :item_id
26
+ attr_reader :item_class
27
+ attr_reader :attributes
28
+
29
+ def initialize(client, item_class, attributes)
30
+ @item_id = ItemId.new(client, attributes[:item_id])
31
+ @item_class = item_class
32
+ @attributes = attributes
33
+ end
34
+
35
+ def [](key)
36
+ @attributes[key]
37
+ end
38
+
39
+ def keys
40
+ @attributes.keys
41
+ end
42
+ end
43
+
44
+ class ItemId
45
+ include Util
46
+
47
+ attr_reader :client
48
+ attr_reader :id
49
+ attr_reader :change_key
50
+
51
+ def initialize(client, item_id)
52
+ @client=client
53
+ @id = item_id[:id]
54
+ @change_key=item_id[:change_key]
55
+ raise "no id" if !@id
56
+ end
57
+
58
+ GET_ITEM_OPTS = {
59
+ :item_shape=>Shape::ITEM_SHAPE_OPTS,
60
+ :ignore_change_keys=>nil
61
+ }
62
+
63
+ def get_item(opts={})
64
+ opts = check_opts(GET_ITEM_OPTS, opts)
65
+ r = with_error_check(client, :get_item_response,:response_messages,:get_item_response_message) do
66
+ client.request(:wsdl, "GetItem") do
67
+ soap.namespaces["xmlns:t"]=SCHEMA_TYPES
68
+
69
+ xml = Builder::XmlMarkup.new
70
+
71
+ xml << Shape::ItemShape.new(opts[:item_shape]||{}).to_xml
72
+ xml.wsdl :ItemIds do
73
+ xml << Gyoku.xml(self.to_xml_hash(opts[:ignore_change_keys]))
74
+ end
75
+
76
+ soap.body = xml.target!
77
+ end
78
+ end
79
+ ::Rews::Item.read_get_item_response_messages(client, r).first
80
+ end
81
+
82
+ DELETE_ITEM_OPTS = {
83
+ :delete_type! =>nil,
84
+ :ignore_change_keys=>false
85
+ }
86
+
87
+ def delete_item(opts={})
88
+ opts = check_opts(DELETE_ITEM_OPTS, opts)
89
+ r = with_error_check(client, :delete_item_response, :response_messages, :delete_item_response_message) do
90
+ client.request(:wsdl, "DeleteItem", :DeleteType=>opts[:delete_type]) do
91
+ soap.namespaces["xmlns:t"]=SCHEMA_TYPES
92
+
93
+ xml = Builder::XmlMarkup.new
94
+
95
+ xml.wsdl :ItemIds do
96
+ xml << Gyoku.xml(self.to_xml_hash(opts[:ignore_change_keys]))
97
+ end
98
+
99
+ soap.body = xml.target!
100
+ end
101
+ end
102
+ true
103
+ end
104
+
105
+ def to_xml_hash(ignore_change_key=false)
106
+ if change_key && !ignore_change_key
107
+ {
108
+ "t:ItemId"=>"",
109
+ :attributes! => {
110
+ "t:ItemId" => {
111
+ "Id" => id.to_s,
112
+ "ChangeKey" => change_key.to_s}}}
113
+ else
114
+ {
115
+ "t:ItemId"=>"",
116
+ :attributes! => {
117
+ "t:ItemId" => {
118
+ "Id" => id.to_s}}}
119
+ end
120
+ end
121
+
122
+ def inspect
123
+ "#{self.class}(id: #{id}, change_key: #{change_key})"
124
+ end
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,89 @@
1
+ # takes restrictions written in Ruby s-expressions and
2
+ # outputs Exchange Web Services Restriction XML
3
+ #
4
+ #
5
+ module Rews
6
+ class Restriction
7
+
8
+ attr_reader :expr
9
+
10
+ def initialize(expr)
11
+ @expr = expr
12
+ end
13
+
14
+ def inspect
15
+ "#{self.class}: #{@expr.inspect}"
16
+ end
17
+
18
+ def to_xml
19
+ Xml::write_restriction(expr)
20
+ end
21
+
22
+ module Xml
23
+ module_function
24
+
25
+ def write_restriction(expr)
26
+ xml = Builder::XmlMarkup.new
27
+ xml.wsdl :Restriction do
28
+ write_expr(xml, expr)
29
+ end
30
+ xml.target!
31
+ end
32
+
33
+ def write_expr(xml, expr)
34
+ case expr[0]
35
+ when :<, :<=, :==, :>=, :>, :"!=" then
36
+ write_comparison(xml, expr)
37
+ when :and, :or, :not then
38
+ write_logical(xml, expr)
39
+ else
40
+ raise "unknown operator: #{expr[0]}"
41
+ end
42
+ end
43
+
44
+ COMPARISON_OPS = {
45
+ :< => :IsLessThan,
46
+ :<= => :IsLessThanOrEqualTo,
47
+ :== => :IsEqualTo,
48
+ :>= => :IsGreaterThanOrEqualTo,
49
+ :> => :IsGreaterThan,
50
+ :"!=" => :IsNotEqualTo}
51
+
52
+ def write_comparison(xml, expr)
53
+ xml.t COMPARISON_OPS[expr[0]] do
54
+ write_field_uri(xml, expr[1])
55
+ write_field_uri_or_constant(xml, expr[2])
56
+ end
57
+ end
58
+
59
+ def write_field_uri_or_constant(xml, expr)
60
+ xml.t :FieldURIOrConstant do
61
+ if expr.is_a?(Array) && expr[0] == :field_uri
62
+ write_field_uri(xml, expr[1])
63
+ else
64
+ write_constant(xml, expr)
65
+ end
66
+ end
67
+ end
68
+
69
+ def write_field_uri(xml, expr)
70
+ xml.t :FieldURI, :FieldURI=>expr
71
+ end
72
+
73
+ def write_constant(xml, expr)
74
+ xml.t :Constant, :Value=>expr
75
+ end
76
+
77
+ LOGICAL_OPS = {
78
+ :and => :And,
79
+ :or => :Or,
80
+ :not => :Not
81
+ }
82
+ def write_logical(xml, expr)
83
+ xml.t LOGICAL_OPS[expr[0]] do
84
+ write_xml(xml ,expr[1..-1])
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
data/lib/rews/shape.rb ADDED
@@ -0,0 +1,70 @@
1
+ module Rews
2
+ module Shape
3
+
4
+ module Xml
5
+ module_function
6
+
7
+ def write_shape(shape_type, &proc)
8
+ xml = Builder::XmlMarkup.new
9
+ xml.wsdl shape_type do
10
+ proc.call(xml)
11
+ end
12
+ xml.target!
13
+ end
14
+
15
+ def write_additional_properties(xml, additional_properties)
16
+ return if !additional_properties
17
+ xml.t :AdditionalProperties do
18
+ additional_properties.each do |additional_property|
19
+ if additional_property[0] == :field_uri
20
+ xml.t :FieldURI, :FieldURI=>additional_property[1]
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+
27
+ class Base
28
+ include Util
29
+ attr_reader :shape
30
+ end
31
+
32
+ ITEM_SHAPE_OPTS = {
33
+ :base_shape=>:Default,
34
+ :include_mime_content=>nil,
35
+ :additional_properties=>nil
36
+ }
37
+
38
+ class ItemShape < Base
39
+ def initialize(shape)
40
+ @shape = check_opts(ITEM_SHAPE_OPTS, shape)
41
+ end
42
+
43
+ def to_xml
44
+ Xml::write_shape(:ItemShape) do |xml|
45
+ xml.t :BaseShape, shape[:base_shape]
46
+ xml.t :IncludeMimeContent, shape[:include_mime_content] if shape[:include_mime_content]
47
+ Xml::write_additional_properties(xml, shape[:additional_properties])
48
+ end
49
+ end
50
+ end
51
+
52
+ FOLDER_SHAPE_OPTS = {
53
+ :base_shape=>:Default,
54
+ :additional_properties=>nil
55
+ }
56
+
57
+ class FolderShape < Base
58
+ def initialize(shape)
59
+ @shape = check_opts(FOLDER_SHAPE_OPTS, shape)
60
+ end
61
+
62
+ def to_xml
63
+ Xml::write_shape(:FolderShape) do |xml|
64
+ xml.t :BaseShape, shape[:base_shape]
65
+ Xml::write_additional_properties(xml, shape[:additional_properties])
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,53 @@
1
+ # takes sort_orders written in Ruby s-expressions and
2
+ # outputs EWS SortOrder XML
3
+ module Rews
4
+ class SortOrder
5
+ attr_reader :expr
6
+
7
+ def initialize(expr)
8
+ @expr=expr
9
+ end
10
+
11
+ def inspect
12
+ "#{self.class}: #{@expr.inspect}"
13
+ end
14
+
15
+ def to_xml
16
+ Xml::write_sort_order(expr)
17
+ end
18
+
19
+ module Xml
20
+ module_function
21
+
22
+ def write_sort_order(expr)
23
+ xml = Builder::XmlMarkup.new
24
+ xml.wsdl :SortOrder do
25
+ write_expr(xml, expr)
26
+ end
27
+ xml.target!
28
+ end
29
+
30
+ def write_expr(xml, expr)
31
+ expr.each do |field_order|
32
+ write_field_order(xml, field_order)
33
+ end
34
+ end
35
+
36
+ def write_field_order(xml, field_order)
37
+ if field_order.is_a?(Array)
38
+ xml.t :FieldOrder, :Order=>field_order[1] do
39
+ write_field_uri(xml, field_order[0])
40
+ end
41
+ else
42
+ xml.t :FieldOrder, field_order do
43
+ write_field_uri(xml, field_order)
44
+ end
45
+ end
46
+ end
47
+
48
+ def write_field_uri(xml, field_uri)
49
+ xml.t :FieldURI, :FieldURI=>field_uri
50
+ end
51
+ end
52
+ end
53
+ end
data/lib/rews/util.rb ADDED
@@ -0,0 +1,86 @@
1
+ require 'set'
2
+
3
+ module Rews
4
+ module Util
5
+ module_function
6
+
7
+ # validates an options hash against a constraints hash
8
+ # in the constraints hash :
9
+ # - keys ending in ! indicate option is required
10
+ # - keys not ending in ! indicate option is not required
11
+ # - non-nil values provide defaults
12
+ # - hash values provide constraints for sub-hashes
13
+ def check_opts(constraints, opts={}, prefix=nil)
14
+ required_keys = Hash[constraints.keys.select{|k| k.to_s[-1..-1] == '!'}.map{|k| [strip_bang(k),k]}]
15
+ optional_keys = constraints.keys.select{|k| k.to_s[-1..-1] != '!'}
16
+
17
+ # check each of the provided options
18
+ opts.keys.each do |key|
19
+ raise "unknown option: #{[prefix,key].compact.join(".")}" if !required_keys.include?(key) && !optional_keys.include?(key)
20
+
21
+ ckey = required_keys[key] || key
22
+ if constraints[ckey].is_a? Hash
23
+ check_opts(constraints[ckey], opts[key] || {}, [prefix,key].compact.join("."))
24
+ end
25
+
26
+ required_keys.delete(key)
27
+ optional_keys.delete(key)
28
+ end
29
+
30
+ raise "required options not given: #{required_keys.keys.map{|k| [prefix,k].compact.join('.')}.join(", ")}" if required_keys.size>0
31
+
32
+ # defaults
33
+ optional_keys.each{|k| opts[k] = constraints[k] if !constraints[k].is_a?(Hash)}
34
+ opts
35
+ end
36
+
37
+ def strip_bang(k)
38
+ if k.is_a? Symbol
39
+ k.to_s[0...-1].to_sym
40
+ else
41
+ k.to_s[0...-1]
42
+ end
43
+ end
44
+
45
+ def camelize(s)
46
+ s.split('_').map(&:capitalize).join
47
+ end
48
+
49
+ def camel_keys(h)
50
+ Hash[h.map{|k,v| [camelize(k.to_s), v]}]
51
+ end
52
+
53
+ def with_error_check(client, *response_msg_keys)
54
+ raise "no block" if !block_given?
55
+
56
+ response = yield
57
+ hash_response = response.to_hash
58
+ statuses = hash_response.fetch_in(*response_msg_keys)
59
+
60
+ if statuses.is_a?(Array)
61
+ all_statuses = statuses
62
+ else
63
+ all_statuses = [statuses]
64
+ end
65
+
66
+ errors = all_statuses.map{|s| single_error_check(client, s)}.compact
67
+ raise errors.join("\n") if !errors.empty?
68
+
69
+ statuses
70
+ end
71
+
72
+ def single_error_check(client, status)
73
+ begin
74
+ response_class = status[:response_class]
75
+ rescue
76
+ raise "no response_class found: #{status.inspect}" if !response_class
77
+ end
78
+
79
+ if status[:response_class] == "Error"
80
+ return "Rews: #{status[:response_code]} - #{status[:message_text]}"
81
+ elsif status[:response_class] == "Warning"
82
+ client.log{|logger| logger.warn("#{status[:message_text]}")}
83
+ end
84
+ end
85
+ end
86
+ end
data/lib/rews/view.rb ADDED
@@ -0,0 +1,43 @@
1
+ module Rews
2
+ module View
3
+ module Xml
4
+ module_function
5
+
6
+ def write_item_view(view_type, attrs, &proc)
7
+ xml = Builder::XmlMarkup.new
8
+ xml.wsdl view_type, Util::camel_keys(attrs) do
9
+ proc.call(xml) if proc
10
+ end
11
+ xml.target!
12
+ end
13
+ end
14
+
15
+ class Base
16
+ include Util
17
+
18
+ attr_reader :view
19
+ end
20
+
21
+ INDEXED_PAGE_VIEW_OPTS = {:max_entries_returned=>nil, :offset=>0, :base_point=>:Beginning}
22
+
23
+ class IndexedPageItemView < Base
24
+ def initialize(view)
25
+ @view = check_opts(INDEXED_PAGE_VIEW_OPTS, view)
26
+ end
27
+
28
+ def to_xml
29
+ Xml::write_item_view(:IndexedPageItemView, view)
30
+ end
31
+ end
32
+
33
+ class IndexedPageFolderView < Base
34
+ def initialize(view)
35
+ @view = check_opts(INDEXED_PAGE_VIEW_OPTS, view)
36
+ end
37
+
38
+ def to_xml
39
+ Xml::write_item_view(:IndexedPageFolderView, view)
40
+ end
41
+ end
42
+ end
43
+ end
data/lib/rews.rb ADDED
@@ -0,0 +1,55 @@
1
+ $: << File.expand_path("..", __FILE__)
2
+
3
+ require 'net/ntlm'
4
+ require 'httpclient'
5
+ require 'savon'
6
+ require 'fetch_in'
7
+
8
+ # Ruby Exchange Web Services
9
+ module Rews
10
+ WSDL = File.expand_path("../../Services.wsdl", __FILE__)
11
+ SCHEMA_MESSAGES = "http://schemas.microsoft.com/exchange/services/2006/messages"
12
+ SCHEMA_TYPES = "http://schemas.microsoft.com/exchange/services/2006/types"
13
+ end
14
+
15
+ require 'rews/util'
16
+ require 'rews/restriction'
17
+ require 'rews/shape'
18
+ require 'rews/sort_order'
19
+ require 'rews/view'
20
+ require 'rews/folder'
21
+ require 'rews/item'
22
+
23
+ module Rews
24
+ class Client
25
+ attr_reader :client
26
+ attr_accessor :logdev
27
+
28
+ # Rews::Client.new('https://exchange.foo.com/EWS/Exchange.asmx', :ntlm, 'DOMAIN\\user', 'password')
29
+ # Rews::Client.new('https://exchange.foo.com/EWS/Exchange.asmx', :basic, 'DOMAIN\\user', 'password')
30
+ def initialize(endpoint, auth_type, user, password)
31
+ @client = Savon::Client.new do
32
+ wsdl.endpoint = endpoint
33
+ wsdl.namespace = SCHEMA_MESSAGES
34
+
35
+ http.auth.ssl.verify_mode = :none
36
+ http.auth.send(auth_type, user, password)
37
+ end
38
+ end
39
+
40
+ # client.distinguished_folder_id('inbox')
41
+ # client.distinguished_folder_id('inbox', 'foo@bar.com') # to get a folder from another mailbox
42
+ def distinguished_folder_id(id, mailbox_email=nil)
43
+ Folder::DistinguishedFolderId.new(client, id, mailbox_email)
44
+ end
45
+
46
+ def log
47
+ yield logger if @logdev
48
+ end
49
+
50
+ def logger
51
+ return @logger if @logger
52
+ @logger = Logger.new(@logdev) if @logdev
53
+ end
54
+ end
55
+ end
data/spec/rews_spec.rb ADDED
@@ -0,0 +1,4 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe Rews do
4
+ end
data/spec/spec.opts ADDED
@@ -0,0 +1 @@
1
+ --color
@@ -0,0 +1,11 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+ require 'rubygems'
4
+ require 'rews'
5
+ require 'spec'
6
+ require 'spec/autorun'
7
+ require 'rr'
8
+
9
+ Spec::Runner.configure do |config|
10
+ config.mock_with RR::Adapters::Rspec
11
+ end
metadata ADDED
@@ -0,0 +1,181 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rews
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ version: 0.1.0
11
+ platform: ruby
12
+ authors:
13
+ - Trampoline Systems Ltd
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-03-14 00:00:00 +00:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: savon
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 51
30
+ segments:
31
+ - 0
32
+ - 8
33
+ - 6
34
+ version: 0.8.6
35
+ type: :runtime
36
+ version_requirements: *id001
37
+ - !ruby/object:Gem::Dependency
38
+ name: ntlm-http
39
+ prerelease: false
40
+ requirement: &id002 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ hash: 25
46
+ segments:
47
+ - 0
48
+ - 1
49
+ - 1
50
+ version: 0.1.1
51
+ type: :runtime
52
+ version_requirements: *id002
53
+ - !ruby/object:Gem::Dependency
54
+ name: httpclient
55
+ prerelease: false
56
+ requirement: &id003 !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ hash: 217
62
+ segments:
63
+ - 2
64
+ - 1
65
+ - 6
66
+ - 1
67
+ - 1
68
+ version: 2.1.6.1.1
69
+ type: :runtime
70
+ version_requirements: *id003
71
+ - !ruby/object:Gem::Dependency
72
+ name: fetch_in
73
+ prerelease: false
74
+ requirement: &id004 !ruby/object:Gem::Requirement
75
+ none: false
76
+ requirements:
77
+ - - ">="
78
+ - !ruby/object:Gem::Version
79
+ hash: 23
80
+ segments:
81
+ - 0
82
+ - 2
83
+ - 0
84
+ version: 0.2.0
85
+ type: :runtime
86
+ version_requirements: *id004
87
+ - !ruby/object:Gem::Dependency
88
+ name: rspec
89
+ prerelease: false
90
+ requirement: &id005 !ruby/object:Gem::Requirement
91
+ none: false
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ hash: 13
96
+ segments:
97
+ - 1
98
+ - 2
99
+ - 9
100
+ version: 1.2.9
101
+ type: :development
102
+ version_requirements: *id005
103
+ - !ruby/object:Gem::Dependency
104
+ name: rr
105
+ prerelease: false
106
+ requirement: &id006 !ruby/object:Gem::Requirement
107
+ none: false
108
+ requirements:
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ hash: 61
112
+ segments:
113
+ - 0
114
+ - 10
115
+ - 5
116
+ version: 0.10.5
117
+ type: :development
118
+ version_requirements: *id006
119
+ description: an email focussed Ruby client for Exchange Web Services atop Savon
120
+ email: craig@trampolinesystems.com
121
+ executables: []
122
+
123
+ extensions: []
124
+
125
+ extra_rdoc_files:
126
+ - LICENSE
127
+ - README.rdoc
128
+ files:
129
+ - .document
130
+ - LICENSE
131
+ - README.rdoc
132
+ - Rakefile
133
+ - VERSION
134
+ - lib/rews.rb
135
+ - lib/rews/folder.rb
136
+ - lib/rews/item.rb
137
+ - lib/rews/restriction.rb
138
+ - lib/rews/shape.rb
139
+ - lib/rews/sort_order.rb
140
+ - lib/rews/util.rb
141
+ - lib/rews/view.rb
142
+ - spec/rews_spec.rb
143
+ - spec/spec.opts
144
+ - spec/spec_helper.rb
145
+ has_rdoc: true
146
+ homepage: http://github.com/trampoline/rews
147
+ licenses: []
148
+
149
+ post_install_message:
150
+ rdoc_options: []
151
+
152
+ require_paths:
153
+ - lib
154
+ required_ruby_version: !ruby/object:Gem::Requirement
155
+ none: false
156
+ requirements:
157
+ - - ">="
158
+ - !ruby/object:Gem::Version
159
+ hash: 3
160
+ segments:
161
+ - 0
162
+ version: "0"
163
+ required_rubygems_version: !ruby/object:Gem::Requirement
164
+ none: false
165
+ requirements:
166
+ - - ">="
167
+ - !ruby/object:Gem::Version
168
+ hash: 3
169
+ segments:
170
+ - 0
171
+ version: "0"
172
+ requirements: []
173
+
174
+ rubyforge_project:
175
+ rubygems_version: 1.5.2
176
+ signing_key:
177
+ specification_version: 3
178
+ summary: a Ruby client for Exchange Web Services
179
+ test_files:
180
+ - spec/rews_spec.rb
181
+ - spec/spec_helper.rb