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 +5 -0
- data/LICENSE +20 -0
- data/README.rdoc +52 -0
- data/Rakefile +50 -0
- data/VERSION +1 -0
- data/lib/rews/folder.rb +274 -0
- data/lib/rews/item.rb +127 -0
- data/lib/rews/restriction.rb +89 -0
- data/lib/rews/shape.rb +70 -0
- data/lib/rews/sort_order.rb +53 -0
- data/lib/rews/util.rb +86 -0
- data/lib/rews/view.rb +43 -0
- data/lib/rews.rb +55 -0
- data/spec/rews_spec.rb +4 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +11 -0
- metadata +181 -0
data/.document
ADDED
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
|
data/lib/rews/folder.rb
ADDED
@@ -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
data/spec/spec.opts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/spec/spec_helper.rb
ADDED
@@ -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
|