rexchange 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +3 -0
- data/RAKEFILE +67 -0
- data/README +76 -0
- data/lib/rexchange.rb +11 -0
- data/lib/rexchange/credentials.rb +27 -0
- data/lib/rexchange/dav_move_request.rb +22 -0
- data/lib/rexchange/dav_search_request.rb +8 -0
- data/lib/rexchange/exchange_request.rb +40 -0
- data/lib/rexchange/extensions.rb +9 -0
- data/lib/rexchange/folder.rb +119 -0
- data/lib/rexchange/message.rb +43 -0
- data/lib/rexchange/session.rb +34 -0
- data/test/functional.rb +28 -0
- metadata +58 -0
data/CHANGELOG
ADDED
data/RAKEFILE
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'rake'
|
5
|
+
require 'rake/rdoctask'
|
6
|
+
require 'rake/gempackagetask'
|
7
|
+
require 'rake/contrib/rubyforgepublisher'
|
8
|
+
# require 'pscp'
|
9
|
+
|
10
|
+
PACKAGE_VERSION = '0.1.1'
|
11
|
+
|
12
|
+
PACKAGE_FILES = FileList[
|
13
|
+
'README',
|
14
|
+
'CHANGELOG',
|
15
|
+
'RAKEFILE',
|
16
|
+
'lib/**/*.rb',
|
17
|
+
'test/*.rb'
|
18
|
+
].to_a
|
19
|
+
|
20
|
+
PROJECT = 'rexchange'
|
21
|
+
|
22
|
+
ENV['RUBYFORGE_USER'] = "ssmoot@rubyforge.org"
|
23
|
+
ENV['RUBYFORGE_PROJECT'] = "/var/www/gforge-projects/#{PROJECT}"
|
24
|
+
|
25
|
+
task :default => [:rdoc]
|
26
|
+
|
27
|
+
desc 'Generate Documentation'
|
28
|
+
rd = Rake::RDocTask.new do |rdoc|
|
29
|
+
rdoc.rdoc_dir = 'doc'
|
30
|
+
rdoc.title = "RExchange -- A simple wrapper around Microsoft Exchange Server's WebDAV API"
|
31
|
+
rdoc.options << '--line-numbers' << '--inline-source' << '--main' << 'README'
|
32
|
+
rdoc.rdoc_files.include(PACKAGE_FILES)
|
33
|
+
end
|
34
|
+
|
35
|
+
gem_spec = Gem::Specification.new do |s|
|
36
|
+
s.platform = Gem::Platform::RUBY
|
37
|
+
s.name = PROJECT
|
38
|
+
s.summary = "A simple wrapper around Microsoft Exchange Server's WebDAV API"
|
39
|
+
s.description = "Connect, browse, and iterate through folders and messages on an Exchange Server"
|
40
|
+
s.version = PACKAGE_VERSION
|
41
|
+
|
42
|
+
s.authors = 'Sam Smoot', 'Scott Bauer'
|
43
|
+
s.email = 'ssmoot@gmail.com; bauer.mail@gmail.com'
|
44
|
+
s.rubyforge_project = PROJECT
|
45
|
+
s.homepage = 'http://substantiality.net'
|
46
|
+
|
47
|
+
s.files = PACKAGE_FILES
|
48
|
+
|
49
|
+
s.require_path = 'lib'
|
50
|
+
s.requirements << 'none'
|
51
|
+
s.autorequire = 'rexchange'
|
52
|
+
|
53
|
+
s.has_rdoc = true
|
54
|
+
s.rdoc_options << '--line-numbers' << '--inline-source' << '--main' << 'README'
|
55
|
+
s.extra_rdoc_files = rd.rdoc_files.reject { |fn| fn =~ /\.rb$/ }.to_a
|
56
|
+
end
|
57
|
+
|
58
|
+
Rake::GemPackageTask.new(gem_spec) do |p|
|
59
|
+
p.gem_spec = gem_spec
|
60
|
+
p.need_tar = true
|
61
|
+
p.need_zip = true
|
62
|
+
end
|
63
|
+
|
64
|
+
desc "Publish RDOC to RubyForge"
|
65
|
+
task :rubyforge => [:rdoc, :gem] do
|
66
|
+
Rake::SshDirPublisher.new(ENV['RUBYFORGE_USER'], ENV['RUBYFORGE_PROJECT'], 'doc').upload
|
67
|
+
end
|
data/README
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
= RExchange -- A pure ruby wrapper for the Microsoft Exchange Server WebDAV API
|
2
|
+
|
3
|
+
== Things you should know
|
4
|
+
|
5
|
+
* Requires Ruby 1.8.4 (for the extended WebDAV support in the net/http library)
|
6
|
+
* RExchange is cross-platform compatible, being written in pure Ruby
|
7
|
+
* Kiwi fruits are packed with vitamins
|
8
|
+
|
9
|
+
== Why should you use RExchange
|
10
|
+
|
11
|
+
* It makes interacting with Exchange simple
|
12
|
+
* It was written for a real application, and does real work reliably day in and day out
|
13
|
+
|
14
|
+
== Example
|
15
|
+
|
16
|
+
uri = 'https://example.com/exchange/admin/'
|
17
|
+
options = { :user => 'mydomain\admin', :password => 'random' }
|
18
|
+
|
19
|
+
# We pass our uri (pointing directly to a mailbox), and options hash to RExchange::open
|
20
|
+
# to create a RExchange::Session.
|
21
|
+
RExchange::open(uri, options) do |mailbox|
|
22
|
+
|
23
|
+
# The block parameter ("mailbox" in this case) is actually the Session itself.
|
24
|
+
# You can refer to folders by chaining them as method calls. "inbox" in this case
|
25
|
+
# isn't a defined method for Session, but part of the DSL to refer to folder names.
|
26
|
+
# Each folder name returns a RExchange::Folder. The folder is Enumerable, allowing
|
27
|
+
# iteration over the messages in the folder.
|
28
|
+
mailbox.inbox.each do |message|
|
29
|
+
|
30
|
+
# The "message" block parameter is a RExchange::Message object. You have access to
|
31
|
+
# several attributes of the message, including: href, from, to, message-id, date,
|
32
|
+
# importance, hasattachment and body.
|
33
|
+
p message.subject
|
34
|
+
|
35
|
+
# The RExchange::Message#move_to method moves the message to another folder, in this
|
36
|
+
# case, an "archive" folder off of the inbox.
|
37
|
+
message.move_to '/inbox/archive'
|
38
|
+
end
|
39
|
+
|
40
|
+
# You can also call the RExchange::Folder#messages method if you find calling "each"
|
41
|
+
# on a folder directly a little obscure.
|
42
|
+
mailbox.inbox.archive.messages.each do |m|
|
43
|
+
|
44
|
+
# Our previous message should show up in here now.
|
45
|
+
p m.subject
|
46
|
+
end
|
47
|
+
|
48
|
+
# The RExchange::Folder#message_in method is less expensive than the DSL folder-chaining
|
49
|
+
# methods. Since the folder-chaining is cached in the Session it's a small hit, and some
|
50
|
+
# may prefer the readability of them, but if you're looking for absolute performance
|
51
|
+
# or have really deep folder structures then this may be the method for you.
|
52
|
+
mailbox.messages_in '/inbox/' do |m|
|
53
|
+
|
54
|
+
# Since we moved all our messages to the archive earlier, this shouldn't display
|
55
|
+
# anything.
|
56
|
+
p m.from
|
57
|
+
end
|
58
|
+
|
59
|
+
# Folder names are "normalized", replacing dashes and spaces with underscores,
|
60
|
+
# squeezing out multiple underscores in a row, and downcasing the whole thing.
|
61
|
+
# So a folder name such as "My Very-long Folder Name" would look like:
|
62
|
+
mailbox.my_very_long_folder_name
|
63
|
+
end
|
64
|
+
|
65
|
+
== Caveats
|
66
|
+
|
67
|
+
There are several features missing (simply because we didn't need them yet). Among them:
|
68
|
+
|
69
|
+
* The ability to delete messages or folders
|
70
|
+
* The ability to create folders
|
71
|
+
* There's no mechanism for sending new messages, or replying or forwarding existing ones
|
72
|
+
* There are a lot more message attributes we're not retrieving since they weren't useful to us, but they might be to you
|
73
|
+
* Exporting an email or entire folder tree to offline storage
|
74
|
+
* And much much more!
|
75
|
+
|
76
|
+
If you'd like to see any of these features, or have some ideas of your own you'd like to see implemented don't hesitate to let us know, and if it strikes our fancy maybe you'll get some free programming!
|
data/lib/rexchange.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'rexchange/session'
|
2
|
+
|
3
|
+
module RExchange
|
4
|
+
# Use STDOUT or another stream if you'd like to capture the HTTP debug output
|
5
|
+
DEBUG_STREAM = nil
|
6
|
+
|
7
|
+
# A shortcut to RExchange::Session#new's block syntax
|
8
|
+
def self.open(uri, options = {})
|
9
|
+
yield RExchange::Session.new(uri, options)
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'uri'
|
2
|
+
|
3
|
+
module RExchange
|
4
|
+
|
5
|
+
# Credentials are passed around between Folders to emulate a stateful
|
6
|
+
# connection with the RExchange::Session
|
7
|
+
class Credentials
|
8
|
+
attr_reader :user, :password, :uri, :use_ssl
|
9
|
+
|
10
|
+
# You must pass a uri, and an options hash containing :user and :password
|
11
|
+
def initialize(uri, options = {})
|
12
|
+
@uri = URI.parse(uri)
|
13
|
+
@use_ssl = (@uri.scheme.downcase == 'https')
|
14
|
+
@user = @uri.userinfo ? @user.userinfo.split(':')[0] : options.delete(:user)
|
15
|
+
@password = @uri.userinfo ? @user.userinfo.split(':')[1] : options.delete(:password)
|
16
|
+
@port = @uri.port || @uri.default_port
|
17
|
+
|
18
|
+
if block_given?
|
19
|
+
yield self
|
20
|
+
else
|
21
|
+
return self
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'rexchange/exchange_request'
|
2
|
+
|
3
|
+
module RExchange
|
4
|
+
# Used to move entities to different locations in the accessable mailbox.
|
5
|
+
class DavMoveRequest < ExchangeRequest
|
6
|
+
METHOD = 'MOVE'
|
7
|
+
REQUEST_HAS_BODY = false
|
8
|
+
RESPONSE_HAS_BODY = false
|
9
|
+
|
10
|
+
def self.execute(credentials, source, destination)
|
11
|
+
options = {
|
12
|
+
:headers => {
|
13
|
+
'Destination' => destination
|
14
|
+
},
|
15
|
+
:path => source
|
16
|
+
}
|
17
|
+
|
18
|
+
super credentials, options
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'net/https'
|
2
|
+
|
3
|
+
module RExchange
|
4
|
+
|
5
|
+
# Exchange Server's WebDAV interface is non-standard, so
|
6
|
+
# we create this simple wrapper to extend the 'net/http'
|
7
|
+
# library and add the request methods we need.
|
8
|
+
class ExchangeRequest < Net::HTTPRequest
|
9
|
+
REQUEST_HAS_BODY = true
|
10
|
+
RESPONSE_HAS_BODY = true
|
11
|
+
|
12
|
+
def self.execute(credentials, options = {})
|
13
|
+
http = Net::HTTP.new(credentials.uri.host, credentials.uri.port)
|
14
|
+
http.set_debug_output(RExchange::DEBUG_STREAM) if RExchange::DEBUG_STREAM
|
15
|
+
request_path = options[:path] || credentials.uri.path
|
16
|
+
req = self.new(request_path)
|
17
|
+
req.basic_auth credentials.user, credentials.password
|
18
|
+
req.content_type = 'text/xml'
|
19
|
+
req.add_field 'host', credentials.uri.host
|
20
|
+
|
21
|
+
if options[:headers]
|
22
|
+
options[:headers].each_pair do |k, v|
|
23
|
+
req.add_field k, v
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
req.body = options[:body] if REQUEST_HAS_BODY
|
28
|
+
http.use_ssl = true
|
29
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
30
|
+
return http.request(req) if RESPONSE_HAS_BODY
|
31
|
+
return true
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
# You can not instantiate an ExchangeRequest externally.
|
36
|
+
def initialize(*args)
|
37
|
+
super
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
require 'rexml/document'
|
2
|
+
require 'net/https'
|
3
|
+
require 'rexchange/extensions'
|
4
|
+
require 'rexchange/dav_search_request'
|
5
|
+
require 'rexchange/message'
|
6
|
+
|
7
|
+
module RExchange
|
8
|
+
|
9
|
+
class Folder
|
10
|
+
include REXML
|
11
|
+
|
12
|
+
include Enumerable
|
13
|
+
|
14
|
+
attr_reader :credentails
|
15
|
+
|
16
|
+
def initialize(credentials, parent, folder)
|
17
|
+
@credentials, @parent, @folder = credentials, parent, folder
|
18
|
+
end
|
19
|
+
|
20
|
+
alias :old_method_missing :method_missing
|
21
|
+
|
22
|
+
def method_missing(sym, *args)
|
23
|
+
if subfolder_exist?(sym.to_s)
|
24
|
+
Folder.new(@credentials, self, sym )
|
25
|
+
else
|
26
|
+
old_method_missing(sym, args)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def each
|
31
|
+
messages.each do |msg|
|
32
|
+
yield msg
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def messages_in(folder)
|
37
|
+
folder.split('/').inject(@credentials.uri.path) do |final_path, current_path|
|
38
|
+
Folder.new(@credentials, final_path, current_path)
|
39
|
+
end.messages
|
40
|
+
end
|
41
|
+
|
42
|
+
def messages
|
43
|
+
|
44
|
+
body = <<DABODY
|
45
|
+
<?xml version="1.0"?>
|
46
|
+
<D:searchrequest xmlns:D = "DAV:">
|
47
|
+
<D:sql>
|
48
|
+
SELECT "DAV:href",
|
49
|
+
"urn:schemas:httpmail:from", "urn:schemas:httpmail:to", "urn:schemas:mailheader:message-id",
|
50
|
+
"urn:schemas:httpmail:subject", "DAV:href", "urn:schemas:httpmail:date",
|
51
|
+
"urn:schemas:httpmail:importance", "urn:schemas:httpmail:hasattachment",
|
52
|
+
"urn:schemas:httpmail:textdescription", "urn:schemas:httpmail:htmldescription"
|
53
|
+
FROM SCOPE('shallow traversal of "#{to_s}"')
|
54
|
+
WHERE "DAV:ishidden" = false
|
55
|
+
AND "DAV:isfolder" = false
|
56
|
+
AND "DAV:contentclass" = 'urn:content-classes:message'
|
57
|
+
</D:sql>
|
58
|
+
</D:searchrequest>
|
59
|
+
DABODY
|
60
|
+
|
61
|
+
response = DavSearchRequest.execute(@credentials, :body => body)
|
62
|
+
|
63
|
+
mail_messages = []
|
64
|
+
xpath_query = "//a:propstat[a:status/text() = 'HTTP/1.1 200 OK']/a:prop"
|
65
|
+
Document.new(response.body).elements.each(xpath_query) do |m|
|
66
|
+
mail_messages << Message.new(@credentials, m)
|
67
|
+
end
|
68
|
+
|
69
|
+
return mail_messages
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.join(*args)
|
73
|
+
args.collect { |f| f.to_s.ensure_ends_with('/') }.to_s.squeeze('/')
|
74
|
+
end
|
75
|
+
|
76
|
+
def folders
|
77
|
+
@folders ||= get_folders
|
78
|
+
end
|
79
|
+
|
80
|
+
def subfolder_exist?(folder)
|
81
|
+
folders.include? normalize_folder_name(folder)
|
82
|
+
end
|
83
|
+
|
84
|
+
def to_s
|
85
|
+
Folder.join(@parent, @folder)
|
86
|
+
end
|
87
|
+
|
88
|
+
private
|
89
|
+
def normalize_folder_name(folder)
|
90
|
+
folder.to_s.tr('- ', '_').squeeze('_').downcase
|
91
|
+
end
|
92
|
+
|
93
|
+
def get_folders
|
94
|
+
request_body = <<DA_QUERY
|
95
|
+
<?xml version="1.0"?>
|
96
|
+
<D:searchrequest xmlns:D = "DAV:">
|
97
|
+
<D:sql>
|
98
|
+
SELECT "DAV:displayname"
|
99
|
+
FROM SCOPE('shallow traversal of "#{to_s}"')
|
100
|
+
WHERE "DAV:ishidden" = false
|
101
|
+
AND "DAV:isfolder" = true
|
102
|
+
</D:sql>
|
103
|
+
</D:searchrequest>
|
104
|
+
DA_QUERY
|
105
|
+
|
106
|
+
response = DavSearchRequest.execute(@credentials, :body => request_body)
|
107
|
+
|
108
|
+
folders = []
|
109
|
+
|
110
|
+
# iterate through folders query and add each normalized name to the folders array.
|
111
|
+
xpath_query = "//a:propstat[a:status/text() = 'HTTP/1.1 200 OK']/a:prop"
|
112
|
+
Document.new(response.body).elements.each(xpath_query) do |m|
|
113
|
+
folders << normalize_folder_name(m.elements['a:displayname'].text)
|
114
|
+
end
|
115
|
+
|
116
|
+
return folders
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'rexml/document'
|
2
|
+
require 'ostruct'
|
3
|
+
require 'rexchange/dav_move_request'
|
4
|
+
|
5
|
+
module RExchange
|
6
|
+
class Message
|
7
|
+
include REXML
|
8
|
+
include Enumerable
|
9
|
+
|
10
|
+
attr_accessor :attributes
|
11
|
+
|
12
|
+
def [](key)
|
13
|
+
return @attributes[key]
|
14
|
+
end
|
15
|
+
|
16
|
+
def method_missing(sym, *args)
|
17
|
+
return @attributes[sym.to_s] if @attributes.include?(sym.to_s)
|
18
|
+
end
|
19
|
+
|
20
|
+
def body
|
21
|
+
@attributes['textdescription'] || @attributes['htmldescription']
|
22
|
+
end
|
23
|
+
|
24
|
+
def initialize(session, dav_property_node)
|
25
|
+
@attributes = {}
|
26
|
+
@session = session
|
27
|
+
|
28
|
+
dav_property_node.elements.each do |element|
|
29
|
+
@attributes[element.name.tr('-', '_')] = element.text
|
30
|
+
end
|
31
|
+
|
32
|
+
return self
|
33
|
+
end
|
34
|
+
|
35
|
+
def move_to(folder)
|
36
|
+
source = URI.parse(self.href).path
|
37
|
+
destination = @session.uri.path.ensure_ends_with('/') + folder.ensure_ends_with('/') + source.split('/').last
|
38
|
+
|
39
|
+
DavMoveRequest.execute(@session, source, destination)
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'rexchange/folder'
|
3
|
+
require 'rexchange/credentials'
|
4
|
+
|
5
|
+
module RExchange
|
6
|
+
|
7
|
+
class Session < Folder
|
8
|
+
|
9
|
+
# Creates a Credentials instance to pass to subfolders
|
10
|
+
# === Example
|
11
|
+
# uri = 'https://mydomain.com/exchange/demo'
|
12
|
+
# options = { :user => 'test', :password => 'random' }
|
13
|
+
#
|
14
|
+
# RExchange::Session.new(uri, options) do |mailbox|
|
15
|
+
# mailbox.test.each do |message|
|
16
|
+
# puts message.subject
|
17
|
+
# end
|
18
|
+
# end
|
19
|
+
def initialize(uri, options = {})
|
20
|
+
|
21
|
+
@credentials = Credentials.new(uri, options)
|
22
|
+
@parent = @credentials.uri.path
|
23
|
+
@folder = ''
|
24
|
+
|
25
|
+
if block_given?
|
26
|
+
yield self
|
27
|
+
else
|
28
|
+
return self
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
data/test/functional.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'rexchange'
|
3
|
+
|
4
|
+
class FunctionalTests < Test::Unit::TestCase
|
5
|
+
|
6
|
+
def setup
|
7
|
+
end
|
8
|
+
|
9
|
+
def teardown
|
10
|
+
end
|
11
|
+
|
12
|
+
# Ok, so it's not a real test, but I needed to get started,
|
13
|
+
# and get rid of console scripts.
|
14
|
+
def test_no_exceptions
|
15
|
+
|
16
|
+
uri = "https://#{ENV['rexchange_test_server']}/exchange/#{ENV['rexchange_test_mailbox']}/"
|
17
|
+
options = { :user => ENV['rexchange_test_user'], :password => ENV['rexchange_test_password'] }
|
18
|
+
|
19
|
+
RExchange::open(uri, options) do |mailbox|
|
20
|
+
mailbox.inbox.each do |message|
|
21
|
+
puts message.body
|
22
|
+
end
|
23
|
+
|
24
|
+
mailbox.folders.each { |folder| puts folder }
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
metadata
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
rubygems_version: 0.8.10
|
3
|
+
specification_version: 1
|
4
|
+
name: rexchange
|
5
|
+
version: !ruby/object:Gem::Version
|
6
|
+
version: 0.1.1
|
7
|
+
date: 2006-01-27
|
8
|
+
summary: "A simple wrapper around Microsoft Exchange Server's WebDAV API"
|
9
|
+
require_paths:
|
10
|
+
- lib
|
11
|
+
email: ssmoot@gmail.com; bauer.mail@gmail.com
|
12
|
+
homepage: http://substantiality.net
|
13
|
+
rubyforge_project: rexchange
|
14
|
+
description: "Connect, browse, and iterate through folders and messages on an Exchange Server"
|
15
|
+
autorequire: rexchange
|
16
|
+
default_executable:
|
17
|
+
bindir: bin
|
18
|
+
has_rdoc: true
|
19
|
+
required_ruby_version: !ruby/object:Gem::Version::Requirement
|
20
|
+
requirements:
|
21
|
+
-
|
22
|
+
- ">"
|
23
|
+
- !ruby/object:Gem::Version
|
24
|
+
version: 0.0.0
|
25
|
+
version:
|
26
|
+
platform: ruby
|
27
|
+
authors:
|
28
|
+
- Sam Smoot
|
29
|
+
- Scott Bauer
|
30
|
+
files:
|
31
|
+
- README
|
32
|
+
- CHANGELOG
|
33
|
+
- RAKEFILE
|
34
|
+
- lib/rexchange.rb
|
35
|
+
- lib/rexchange/credentials.rb
|
36
|
+
- lib/rexchange/dav_move_request.rb
|
37
|
+
- lib/rexchange/dav_search_request.rb
|
38
|
+
- lib/rexchange/exchange_request.rb
|
39
|
+
- lib/rexchange/extensions.rb
|
40
|
+
- lib/rexchange/folder.rb
|
41
|
+
- lib/rexchange/message.rb
|
42
|
+
- lib/rexchange/session.rb
|
43
|
+
- test/functional.rb
|
44
|
+
test_files: []
|
45
|
+
rdoc_options:
|
46
|
+
- "--line-numbers"
|
47
|
+
- "--inline-source"
|
48
|
+
- "--main"
|
49
|
+
- README
|
50
|
+
extra_rdoc_files:
|
51
|
+
- README
|
52
|
+
- CHANGELOG
|
53
|
+
- RAKEFILE
|
54
|
+
executables: []
|
55
|
+
extensions: []
|
56
|
+
requirements:
|
57
|
+
- none
|
58
|
+
dependencies: []
|