dm-imap-adapter 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/README +66 -0
- data/Rakefile +14 -0
- data/VERSION +1 -0
- data/dm-imap-adapter.gemspec +53 -0
- data/lib/dm-imap-adapter/net_imap_ext.rb +45 -0
- data/lib/dm-imap-adapter/types.rb +209 -0
- data/lib/dm-imap-adapter.rb +174 -0
- data/spec/.bacon +0 -0
- data/spec/dm-imap-adapter_spec.rb +83 -0
- data/spec/spec_helper.rb +71 -0
- data/spec/watchr.rb +39 -0
- metadata +68 -0
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
pkg/*
|
data/README
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
IMAP Adapter for DataMapper
|
2
|
+
===========================
|
3
|
+
|
4
|
+
There exist other implementations of an imap adapter but they are either not 0.10 compatible or I couldn't find a working version. Also, I am picky and want to do this right.
|
5
|
+
|
6
|
+
Philosophy
|
7
|
+
==========
|
8
|
+
|
9
|
+
A resource is a mailbox. So you will probably end up with a Gmail::Inbox and a Gmail::Trash. To move an item to the trash you would resource.move_to(Gmail::Trash) [not implemented yet]. Various Types are provided to make it easier to work with the different things like From and To and CC and all of that. These also help with the queries [not implemented yet].
|
10
|
+
|
11
|
+
Specs
|
12
|
+
=====
|
13
|
+
|
14
|
+
I use bacon. It's the best testing library that exists.
|
15
|
+
|
16
|
+
gem install bacon
|
17
|
+
|
18
|
+
bacon spec/dm-imap-adapter_spec.rb
|
19
|
+
|
20
|
+
or use autotest (although I'm not sure I got that setup perfectly).
|
21
|
+
|
22
|
+
The specs currently assume you have a test account with test as the password on localhost which has an INBOX. All emails in this inbox will be deleted, just like a test database.
|
23
|
+
|
24
|
+
How to use
|
25
|
+
==========
|
26
|
+
|
27
|
+
Check the specs!
|
28
|
+
|
29
|
+
Also, do this for your model:
|
30
|
+
|
31
|
+
class Inbox
|
32
|
+
include DataMapper::Resource
|
33
|
+
include DataMapper::Types::Imap
|
34
|
+
|
35
|
+
def self.default_repository_name
|
36
|
+
:test_inbox
|
37
|
+
end
|
38
|
+
|
39
|
+
property :uid, UID, :key => true # do we need :key here since UID is a serial?
|
40
|
+
property :message_id, MessageId
|
41
|
+
property :subject, Subject
|
42
|
+
property :sender, Sender
|
43
|
+
property :from, From
|
44
|
+
property :to, To
|
45
|
+
property :body, BodyText
|
46
|
+
property :raw_body, Body
|
47
|
+
property :date, InternalDate
|
48
|
+
property :envelope_date, EnvelopeDate
|
49
|
+
property :size, Size
|
50
|
+
property :sequence, Sequence
|
51
|
+
end
|
52
|
+
|
53
|
+
How can I help?
|
54
|
+
===============
|
55
|
+
|
56
|
+
Glad you asked!
|
57
|
+
|
58
|
+
There are lots of things that don't work yet, mostly because I don't need them to (all I need is #all and #destroy). Here are the big things that don't work yet:
|
59
|
+
|
60
|
+
* #update
|
61
|
+
* #create should use update to set any attributes that can't be set during the initial append to the mailbox
|
62
|
+
* Flags - a Type needs to be created that manages an array of symbols. Maybe base it off the Enum?
|
63
|
+
* Searching should actually use the uid_search method and not rely on the in memory query filter. If you got 1000 emails in your inbox your currently screwed.
|
64
|
+
* Figuring out why I had to hack net/imap so much to support dovecot on my machine. Seems strange that I had to open the class like that.
|
65
|
+
* Extend other net/imap structs like the Address one.
|
66
|
+
* Better specs!
|
data/Rakefile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
begin
|
2
|
+
require 'jeweler'
|
3
|
+
Jeweler::Tasks.new do |gemspec|
|
4
|
+
gemspec.name = "dm-imap-adapter"
|
5
|
+
gemspec.summary = "An IMAP adapter for DM"
|
6
|
+
gemspec.description = "DataMapper IMAP adapter"
|
7
|
+
gemspec.email = "nathan@myobie.com"
|
8
|
+
gemspec.homepage = "http://github.com/myobie/dm-imap-adapter"
|
9
|
+
gemspec.authors = ["Nathan Herald"]
|
10
|
+
end
|
11
|
+
Jeweler::GemcutterTasks.new
|
12
|
+
rescue LoadError
|
13
|
+
puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
14
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.1
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{dm-imap-adapter}
|
8
|
+
s.version = "0.0.1"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Nathan Herald"]
|
12
|
+
s.date = %q{2009-10-20}
|
13
|
+
s.description = %q{DataMapper IMAP adapter}
|
14
|
+
s.email = %q{nathan@myobie.com}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"README"
|
17
|
+
]
|
18
|
+
s.files = [
|
19
|
+
".gitignore",
|
20
|
+
"README",
|
21
|
+
"Rakefile",
|
22
|
+
"VERSION",
|
23
|
+
"dm-imap-adapter.gemspec",
|
24
|
+
"lib/dm-imap-adapter.rb",
|
25
|
+
"lib/dm-imap-adapter/net_imap_ext.rb",
|
26
|
+
"lib/dm-imap-adapter/types.rb",
|
27
|
+
"spec/.bacon",
|
28
|
+
"spec/dm-imap-adapter_spec.rb",
|
29
|
+
"spec/spec_helper.rb",
|
30
|
+
"spec/watchr.rb"
|
31
|
+
]
|
32
|
+
s.homepage = %q{http://github.com/myobie/dm-imap-adapter}
|
33
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
34
|
+
s.require_paths = ["lib"]
|
35
|
+
s.rubygems_version = %q{1.3.5}
|
36
|
+
s.summary = %q{An IMAP adapter for DM}
|
37
|
+
s.test_files = [
|
38
|
+
"spec/dm-imap-adapter_spec.rb",
|
39
|
+
"spec/spec_helper.rb",
|
40
|
+
"spec/watchr.rb"
|
41
|
+
]
|
42
|
+
|
43
|
+
if s.respond_to? :specification_version then
|
44
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
45
|
+
s.specification_version = 3
|
46
|
+
|
47
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
48
|
+
else
|
49
|
+
end
|
50
|
+
else
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'net/imap'
|
2
|
+
|
3
|
+
class Net::IMAP::ResponseParser
|
4
|
+
def resp_text_code
|
5
|
+
@lex_state = EXPR_BEG
|
6
|
+
match(T_LBRA)
|
7
|
+
token = match(T_ATOM)
|
8
|
+
name = token.value.upcase
|
9
|
+
case name
|
10
|
+
when /\A(?:ALERT|PARSE|READ-ONLY|READ-WRITE|TRYCREATE|NOMODSEQ|CLOSED)\z/n
|
11
|
+
result = Net::IMAP::ResponseCode.new(name, nil)
|
12
|
+
when /\A(?:PERMANENTFLAGS)\z/n
|
13
|
+
match(T_SPACE)
|
14
|
+
result = Net::IMAP::ResponseCode.new(name, flag_list)
|
15
|
+
when /\A(?:UIDVALIDITY|UIDNEXT|UNSEEN)\z/n
|
16
|
+
match(T_SPACE)
|
17
|
+
result = Net::IMAP::ResponseCode.new(name, number)
|
18
|
+
else
|
19
|
+
# match(T_SPACE)
|
20
|
+
### start new
|
21
|
+
if match(T_SPACE, T_RBRA).symbol == T_RBRA
|
22
|
+
@lex_state = EXPR_RTEXT
|
23
|
+
return Net::IMAP::ResponseCode.new(name, nil)
|
24
|
+
end
|
25
|
+
### end new
|
26
|
+
@lex_state = EXPR_CTEXT
|
27
|
+
token = match(T_TEXT)
|
28
|
+
@lex_state = EXPR_BEG
|
29
|
+
result = Net::IMAP::ResponseCode.new(name, token.value)
|
30
|
+
end
|
31
|
+
match(T_RBRA)
|
32
|
+
@lex_state = EXPR_RTEXT
|
33
|
+
return result
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class Net::IMAP::Address
|
38
|
+
def email
|
39
|
+
"#{mailbox}@#{host}"
|
40
|
+
end
|
41
|
+
|
42
|
+
def to_s
|
43
|
+
"#{name} <#{email}>".strip
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,209 @@
|
|
1
|
+
module DataMapper
|
2
|
+
module Types
|
3
|
+
module Imap
|
4
|
+
module NetIMAPAddressType
|
5
|
+
def self.load(value, property)
|
6
|
+
typecast(value, property)
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.dump(value, property)
|
10
|
+
typecast(value, property)
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.typecast(value, property)
|
14
|
+
if value.nil? || value.empty?
|
15
|
+
[]
|
16
|
+
else
|
17
|
+
# TODO: don't just test the first element of the array
|
18
|
+
if value.is_a?(Array) && value.first.is_a?(Net::IMAP::Address)
|
19
|
+
value
|
20
|
+
else
|
21
|
+
value = [value].flatten
|
22
|
+
|
23
|
+
value.map do |val|
|
24
|
+
|
25
|
+
if val =~ /<.*>$/
|
26
|
+
matches = val.match(/(.*)<(.*)@(.*)>/)
|
27
|
+
if matches
|
28
|
+
n = Net::IMAP::Address.new
|
29
|
+
n.name = matches[0].strip
|
30
|
+
n.mailbox = matches[1].strip
|
31
|
+
n.host = matches[2].strip
|
32
|
+
n
|
33
|
+
else
|
34
|
+
nil
|
35
|
+
end#matches
|
36
|
+
else
|
37
|
+
matches = val.split "@"
|
38
|
+
if matches
|
39
|
+
n = Net::IMAP::Address.new
|
40
|
+
n.mailbox = matches[0].strip
|
41
|
+
n.host = matches[1].strip
|
42
|
+
n
|
43
|
+
else
|
44
|
+
nil
|
45
|
+
end#matches
|
46
|
+
end#val =~
|
47
|
+
|
48
|
+
end.compact #end of each
|
49
|
+
end#value.is_a?
|
50
|
+
end#if value.nil?
|
51
|
+
end#self.typecast
|
52
|
+
end#Net::
|
53
|
+
end#Imap
|
54
|
+
end#Types
|
55
|
+
end#DataMapper
|
56
|
+
|
57
|
+
module DataMapper
|
58
|
+
module Types
|
59
|
+
|
60
|
+
module Imap
|
61
|
+
|
62
|
+
class ImapType < DataMapper::Type
|
63
|
+
class << self
|
64
|
+
attr_reader :query_details, :envelope_name, :attr_name, :method_name
|
65
|
+
|
66
|
+
def imap_query(name)
|
67
|
+
@query_details = name
|
68
|
+
end
|
69
|
+
|
70
|
+
def envelope(name)
|
71
|
+
attr "ENVELOPE"
|
72
|
+
@envelope_name = name
|
73
|
+
end
|
74
|
+
|
75
|
+
def envelope?
|
76
|
+
!!@envelope_name
|
77
|
+
end
|
78
|
+
|
79
|
+
def attr(name)
|
80
|
+
meth :attr
|
81
|
+
@attr_name = name
|
82
|
+
end
|
83
|
+
|
84
|
+
def meth(name)
|
85
|
+
@method_name = name
|
86
|
+
end
|
87
|
+
|
88
|
+
def attr?
|
89
|
+
!!@attr_name
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end#ImapType
|
93
|
+
|
94
|
+
class Sequence < ImapType
|
95
|
+
primitive Integer
|
96
|
+
meth :seqno
|
97
|
+
end
|
98
|
+
|
99
|
+
class Uid < ImapType
|
100
|
+
primitive Integer
|
101
|
+
serial true
|
102
|
+
min 1
|
103
|
+
attr "UID"
|
104
|
+
imap_query(:eql => ["UID"], :like => ["UID"])
|
105
|
+
end
|
106
|
+
UID = Uid
|
107
|
+
|
108
|
+
class BodyText < ImapType
|
109
|
+
primitive String
|
110
|
+
attr "RFC822.TEXT"
|
111
|
+
imap_query(:eql => ["BODY"], :like => ["BODY"])
|
112
|
+
end
|
113
|
+
|
114
|
+
class Body < ImapType
|
115
|
+
primitive String
|
116
|
+
attr "RFC822"
|
117
|
+
imap_query(:eql => ["RFC822"], :like => ["RFC822"])
|
118
|
+
end
|
119
|
+
|
120
|
+
class InternalDate < ImapType
|
121
|
+
primitive DateTime
|
122
|
+
attr "INTERNALDATE"
|
123
|
+
imap_query(:lt => ["BEFORE"], :eql => ["ON"], :gt => ["SINCE"])
|
124
|
+
end
|
125
|
+
|
126
|
+
class EnvelopeDate < ImapType
|
127
|
+
primitive DateTime
|
128
|
+
envelope :date
|
129
|
+
imap_query(:lt => ["SENTBEFORE"], :eql => ["SENTON"], :gt => ["SENTSINCE"])
|
130
|
+
end
|
131
|
+
|
132
|
+
class Size < ImapType
|
133
|
+
primitive Integer
|
134
|
+
attr "RFC822.SIZE"
|
135
|
+
imap_query(:lt => ["SMALLER"], :gt => ["LARGER"])
|
136
|
+
end
|
137
|
+
|
138
|
+
class Header < ImapType
|
139
|
+
primitive String
|
140
|
+
attr "RFC822.HEADER"
|
141
|
+
imap_query(:eql => ["HEADER"])
|
142
|
+
end
|
143
|
+
|
144
|
+
class From < ImapType
|
145
|
+
primitive ::Object
|
146
|
+
envelope :from
|
147
|
+
imap_query(:eql => ["FROM"], :like => ["FROM"])
|
148
|
+
include DataMapper::Types::Imap::NetIMAPAddressType
|
149
|
+
end
|
150
|
+
|
151
|
+
class Sender < ImapType
|
152
|
+
primitive ::Object
|
153
|
+
envelope :sender
|
154
|
+
imap_query(:eql => ["HEADER", "Sender"], :like => ["HEADER", "Sender"])
|
155
|
+
include DataMapper::Types::Imap::NetIMAPAddressType
|
156
|
+
end
|
157
|
+
|
158
|
+
class ReplyTo < ImapType
|
159
|
+
primitive ::Object
|
160
|
+
envelope :reply_to
|
161
|
+
imap_query(:eql => ["HEADER", "Reply-To"], :like => ["HEADER", "Reply-To"])
|
162
|
+
include DataMapper::Types::Imap::NetIMAPAddressType
|
163
|
+
end
|
164
|
+
|
165
|
+
class Cc < ImapType
|
166
|
+
primitive ::Object
|
167
|
+
envelope :cc
|
168
|
+
imap_query(:eql => ["CC"], :like => ["CC"])
|
169
|
+
include DataMapper::Types::Imap::NetIMAPAddressType
|
170
|
+
end
|
171
|
+
CC = Cc
|
172
|
+
|
173
|
+
class To < ImapType
|
174
|
+
primitive ::Object
|
175
|
+
envelope :to
|
176
|
+
imap_query(:eql => ["TO"], :like => ["TO"])
|
177
|
+
include DataMapper::Types::Imap::NetIMAPAddressType
|
178
|
+
end
|
179
|
+
|
180
|
+
class Bcc < ImapType
|
181
|
+
primitive ::Object
|
182
|
+
envelope :bcc
|
183
|
+
imap_query(:eql => ["BCC"], :like => ["BCC"])
|
184
|
+
include DataMapper::Types::Imap::NetIMAPAddressType
|
185
|
+
end
|
186
|
+
BCC = Bcc
|
187
|
+
|
188
|
+
class Subject < ImapType
|
189
|
+
primitive String
|
190
|
+
envelope :subject
|
191
|
+
imap_query(:eql => ["SUBJECT"], :like => ["SUBJECT"])
|
192
|
+
end
|
193
|
+
|
194
|
+
class InReplyTo < ImapType
|
195
|
+
primitive String
|
196
|
+
envelope :in_reply_to
|
197
|
+
imap_query(:eql => ["HEADER", "In-Reply-To"], :like => ["HEADER", "In-Reply-To"])
|
198
|
+
end
|
199
|
+
|
200
|
+
class MessageId < ImapType
|
201
|
+
primitive String
|
202
|
+
envelope :message_id
|
203
|
+
imap_query(:eql => ["HEADER", "Message-ID"], :like => ["HEADER", "Message-ID"])
|
204
|
+
end
|
205
|
+
|
206
|
+
end#Imap
|
207
|
+
|
208
|
+
end#Types
|
209
|
+
end#DataMapper
|
@@ -0,0 +1,174 @@
|
|
1
|
+
require 'dm-core'
|
2
|
+
require 'net/imap'
|
3
|
+
require File.dirname(__FILE__) + '/dm-imap-adapter/net_imap_ext'
|
4
|
+
require File.dirname(__FILE__) + '/dm-imap-adapter/types'
|
5
|
+
|
6
|
+
### Hack for frozen object problem
|
7
|
+
|
8
|
+
module DataMapper
|
9
|
+
module Resource
|
10
|
+
def original_attributes
|
11
|
+
@original_attributes || {} # Fix for frozen crap
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
###
|
17
|
+
|
18
|
+
module DataMapper
|
19
|
+
module Adapters
|
20
|
+
|
21
|
+
class ImapAdapter < AbstractAdapter
|
22
|
+
|
23
|
+
def create(resources)
|
24
|
+
resources.map do |resource|
|
25
|
+
with_connection(resource.model) do |connection|
|
26
|
+
|
27
|
+
last_email = connection.uid_fetch(-1, "UID")
|
28
|
+
if last_email
|
29
|
+
next_uid = last_email.first.attr[:UID].to_i
|
30
|
+
else
|
31
|
+
next_uid = 1
|
32
|
+
end
|
33
|
+
|
34
|
+
initialize_serial(resource, next_uid)
|
35
|
+
|
36
|
+
# email_text = <<EOT.gsub(/\n/, "\r\n")
|
37
|
+
# Subject: #{resource.subject}
|
38
|
+
# From: #{resource.from}
|
39
|
+
# To: #{resource.to}
|
40
|
+
#
|
41
|
+
# #{resource.body}
|
42
|
+
# EOT
|
43
|
+
#
|
44
|
+
email_text = ""
|
45
|
+
|
46
|
+
email_text << "Subject: #{resource.subject}\n" if resource.respond_to?(:subject)
|
47
|
+
email_text << "From: #{resource.from}\n" if resource.respond_to?(:from)
|
48
|
+
email_text << "To: #{resource.to}\n" if resource.respond_to?(:to)
|
49
|
+
email_text << "\n#{resource.body}" if resource.respond_to?(:body)
|
50
|
+
|
51
|
+
email_text = email_text.gsub(/\n/, "\r\n")
|
52
|
+
|
53
|
+
if resource.respond_to?(:date)
|
54
|
+
date = resource.date
|
55
|
+
else
|
56
|
+
date = Time.now
|
57
|
+
end
|
58
|
+
|
59
|
+
if resource.respond_to?(:flags)
|
60
|
+
flags = resources.flags
|
61
|
+
else
|
62
|
+
flags = []
|
63
|
+
end
|
64
|
+
|
65
|
+
connection.append(@options[:path].gsub(%r{^/}, ""), email_text, flags, date)
|
66
|
+
|
67
|
+
end
|
68
|
+
end.size
|
69
|
+
end
|
70
|
+
|
71
|
+
def read(query)
|
72
|
+
with_connection(query.model) do |connection|
|
73
|
+
|
74
|
+
attr_props = query.model.properties.select { |prop| prop.type.attr? }
|
75
|
+
attrs = attr_props.collect { |prop| prop.type.attr_name }.compact.uniq
|
76
|
+
|
77
|
+
# TODO: don't do 1..-1, but instead do a search and then fetch the uids returned
|
78
|
+
uids = 1..-1
|
79
|
+
|
80
|
+
# if query.conditions
|
81
|
+
# query_array = []
|
82
|
+
# debugger
|
83
|
+
# query.conditions.each do |op, property, value|
|
84
|
+
# query_array += (property.type.query_details[op] + [value])
|
85
|
+
# end
|
86
|
+
# uids = connection.uid_search(query_array)
|
87
|
+
# else
|
88
|
+
# uids = 1..-1
|
89
|
+
# end
|
90
|
+
|
91
|
+
mails = connection.uid_fetch(uids, attrs) || []
|
92
|
+
|
93
|
+
results = materialize_records_for(query.model, mails)
|
94
|
+
query.filter_records(results.dup)
|
95
|
+
# results.dup
|
96
|
+
|
97
|
+
end#with_connection
|
98
|
+
end#read
|
99
|
+
|
100
|
+
def update(attributes, collection)
|
101
|
+
raise NotImplemented
|
102
|
+
end
|
103
|
+
|
104
|
+
def delete(collection)
|
105
|
+
with_connection(collection.query.model) do |connection|
|
106
|
+
connection.uid_store(collection.collect { |record| record.uid }, "+FLAGS", [:Deleted])
|
107
|
+
connection.expunge
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
private
|
112
|
+
|
113
|
+
def initialize(name, options = {})
|
114
|
+
super
|
115
|
+
# @connection = create_connection
|
116
|
+
end
|
117
|
+
|
118
|
+
protected
|
119
|
+
def create_connection(model)
|
120
|
+
args = [@options[:host], @options[:port], @options[:ssl]].compact
|
121
|
+
imap = Net::IMAP.new(*args)
|
122
|
+
begin
|
123
|
+
imap.authenticate "login"
|
124
|
+
rescue
|
125
|
+
ensure
|
126
|
+
imap.login(@options[:user], @options[:password])
|
127
|
+
end
|
128
|
+
mailbox = @options[:path].gsub(%r{^/}, "")
|
129
|
+
imap.select(mailbox) rescue raise("Mailbox #{mailbox} not found")
|
130
|
+
imap
|
131
|
+
end
|
132
|
+
|
133
|
+
def with_connection(model)
|
134
|
+
begin
|
135
|
+
connection = create_connection(model)
|
136
|
+
return yield(connection)
|
137
|
+
rescue => error
|
138
|
+
DataMapper.logger.error(error.to_s)
|
139
|
+
raise error
|
140
|
+
ensure
|
141
|
+
close_connection(connection) if connection
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def close_connection(connection)
|
146
|
+
connection.disconnect
|
147
|
+
end
|
148
|
+
|
149
|
+
def materialize_records_for(model, mails)
|
150
|
+
# Array
|
151
|
+
mails.collect do |mail|
|
152
|
+
# of Hashes (one hash per loop)
|
153
|
+
model.properties.inject({}) do |hash, prop|
|
154
|
+
hash[prop.field] = mail.send(prop.type.method_name.to_sym)
|
155
|
+
|
156
|
+
if prop.type.attr?
|
157
|
+
hash[prop.field] = hash[prop.field][prop.type.attr_name]
|
158
|
+
end#if
|
159
|
+
|
160
|
+
if prop.type.envelope?
|
161
|
+
hash[prop.field] = hash[prop.field][prop.type.envelope_name]
|
162
|
+
end
|
163
|
+
|
164
|
+
hash
|
165
|
+
end#inject
|
166
|
+
end#collect
|
167
|
+
end#materialize_records_for
|
168
|
+
|
169
|
+
end#ImapAdapter
|
170
|
+
|
171
|
+
const_added(:ImapAdapter)
|
172
|
+
|
173
|
+
end#Adapters
|
174
|
+
end#DataMapper
|
data/spec/.bacon
ADDED
File without changes
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
class LocalInbox
|
4
|
+
include DataMapper::Resource
|
5
|
+
include DataMapper::Types::Imap
|
6
|
+
|
7
|
+
def self.default_repository_name
|
8
|
+
:test_inbox
|
9
|
+
end
|
10
|
+
|
11
|
+
property :uid, UID, :key => true
|
12
|
+
property :message_id, MessageId
|
13
|
+
property :subject, Subject
|
14
|
+
property :sender, Sender
|
15
|
+
property :from, From
|
16
|
+
property :to, To
|
17
|
+
property :body, BodyText
|
18
|
+
property :raw_body, Body
|
19
|
+
property :date, InternalDate
|
20
|
+
property :envelope_date, EnvelopeDate
|
21
|
+
property :size, Size
|
22
|
+
property :sequence, Sequence
|
23
|
+
end
|
24
|
+
|
25
|
+
describe DataMapper::Adapters::ImapAdapter do
|
26
|
+
|
27
|
+
before do
|
28
|
+
LocalInbox.all.map { |l| l.destroy }
|
29
|
+
|
30
|
+
LocalInbox.create :to => "test@localhost",
|
31
|
+
:from => "Me <me@example.com>",
|
32
|
+
:subject => "Test email 1",
|
33
|
+
:body => "Hello there, how are you?"
|
34
|
+
|
35
|
+
LocalInbox.create :to => "test@localhost",
|
36
|
+
:from => "me@example.com",
|
37
|
+
:subject => "Test email 2 boo ya",
|
38
|
+
:body => "Hi,\n\nTest."
|
39
|
+
|
40
|
+
LocalInbox.create :to => "test@localhost",
|
41
|
+
:from => "Me <me@example.com>",
|
42
|
+
:subject => "Test email 3",
|
43
|
+
:body => "Hello there."
|
44
|
+
|
45
|
+
LocalInbox.create :to => "test@localhost",
|
46
|
+
:from => "me@example.com",
|
47
|
+
:subject => "Test email 4 rabbit",
|
48
|
+
:body => "Hi,\n\nHow's it going?\n\nGive us a call.\n\nTest."
|
49
|
+
end
|
50
|
+
|
51
|
+
should "successfully connect to the local imap server" do
|
52
|
+
lambda { LocalInbox.all }.should.not.raise
|
53
|
+
end
|
54
|
+
|
55
|
+
should "have two emails in the inbox" do
|
56
|
+
LocalInbox.all.length.should == 4
|
57
|
+
end
|
58
|
+
|
59
|
+
should "know who sent the email" do
|
60
|
+
LocalInbox.first.from.first.name.should == "Me"
|
61
|
+
LocalInbox.first.from.first.email.should == "me@example.com"
|
62
|
+
LocalInbox.first.from.first.to_s.should == "Me <me@example.com>"
|
63
|
+
end
|
64
|
+
|
65
|
+
should "know how the email was sent to" do
|
66
|
+
LocalInbox.first.to.first.to_s.should == "<test@localhost>"
|
67
|
+
end
|
68
|
+
|
69
|
+
should "create a new email" do
|
70
|
+
lambda {
|
71
|
+
LocalInbox.create(:to => "test", :from => "test", :subject => "Test", :body => "Test")
|
72
|
+
}.should.increase { LocalInbox.all.length }
|
73
|
+
end
|
74
|
+
|
75
|
+
should "destroy an email" do
|
76
|
+
lambda { LocalInbox.first.destroy }.should.decrease { LocalInbox.all.length }
|
77
|
+
end
|
78
|
+
|
79
|
+
should "find the email with the subject containing 'rabbit'" do
|
80
|
+
LocalInbox.first(:subject.like => "rabbit").subject.should == "Test email 4 rabbit"
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
require "dm-core"
|
2
|
+
require "bacon"
|
3
|
+
require "mocha"
|
4
|
+
require File.dirname(__FILE__) + "/../lib/dm-imap-adapter"
|
5
|
+
|
6
|
+
DataMapper.setup(:test_inbox, "imap://test:test@localhost/INBOX")
|
7
|
+
|
8
|
+
class Proc
|
9
|
+
def increase?
|
10
|
+
pre_result = yield
|
11
|
+
called = call
|
12
|
+
post_result = yield
|
13
|
+
pre_result < post_result
|
14
|
+
end
|
15
|
+
|
16
|
+
def decrease?
|
17
|
+
pre_result = yield
|
18
|
+
called = call
|
19
|
+
post_result = yield
|
20
|
+
pre_result > post_result
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# from 1 to 10 how likely, 1 being not very likely and 10 being all the time
|
25
|
+
def do_i?(i = 5)
|
26
|
+
rand(500) < 50 * i
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
|
31
|
+
|
32
|
+
##
|
33
|
+
# Hash additions.
|
34
|
+
#
|
35
|
+
# From
|
36
|
+
# * http://wincent.com/knowledge-base/Fixtures_considered_harmful%3F
|
37
|
+
# * Neil Rahilly
|
38
|
+
|
39
|
+
class Hash
|
40
|
+
|
41
|
+
##
|
42
|
+
# Filter keys out of a Hash.
|
43
|
+
#
|
44
|
+
# { :a => 1, :b => 2, :c => 3 }.except(:a)
|
45
|
+
# => { :b => 2, :c => 3 }
|
46
|
+
|
47
|
+
def except(*keys)
|
48
|
+
self.reject { |k,v| keys.include?(k || k.to_sym) }
|
49
|
+
end
|
50
|
+
|
51
|
+
##
|
52
|
+
# Override some keys.
|
53
|
+
#
|
54
|
+
# { :a => 1, :b => 2, :c => 3 }.with(:a => 4)
|
55
|
+
# => { :a => 4, :b => 2, :c => 3 }
|
56
|
+
|
57
|
+
def with(overrides = {})
|
58
|
+
self.merge overrides
|
59
|
+
end
|
60
|
+
|
61
|
+
##
|
62
|
+
# Returns a Hash with only the pairs identified by +keys+.
|
63
|
+
#
|
64
|
+
# { :a => 1, :b => 2, :c => 3 }.only(:a)
|
65
|
+
# => { :a => 1 }
|
66
|
+
|
67
|
+
def only(*keys)
|
68
|
+
self.reject { |k,v| !keys.include?(k || k.to_sym) }
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
data/spec/watchr.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
# Run me with:
|
2
|
+
#
|
3
|
+
# $ watchr spec/watchr.rb
|
4
|
+
|
5
|
+
# --------------------------------------------------
|
6
|
+
# Convenience Methods
|
7
|
+
# --------------------------------------------------
|
8
|
+
def all_test_files
|
9
|
+
Dir['spec/**/*_spec.rb']
|
10
|
+
end
|
11
|
+
|
12
|
+
def run(cmd)
|
13
|
+
puts(cmd)
|
14
|
+
system(cmd)
|
15
|
+
end
|
16
|
+
|
17
|
+
def run_all_tests
|
18
|
+
cmd = "bacon '%w( #{all_test_files.join(' ')} ).each {|file| require file }'"
|
19
|
+
run(cmd)
|
20
|
+
end
|
21
|
+
|
22
|
+
# --------------------------------------------------
|
23
|
+
# Watchr Rules
|
24
|
+
# --------------------------------------------------
|
25
|
+
watch( '^spec.*/.*_spec\.rb' ) { |m| run( "bacon %s" % m[0] ) }
|
26
|
+
watch( '^lib/(.*)\.rb' ) { |m| run( "bacon spec/%s_spec.rb" % m[1] ) }
|
27
|
+
watch( '^spec/spec_helper\.rb' ) { run_all_tests }
|
28
|
+
|
29
|
+
# --------------------------------------------------
|
30
|
+
# Signal Handling
|
31
|
+
# --------------------------------------------------
|
32
|
+
# Ctrl-\
|
33
|
+
Signal.trap('QUIT') do
|
34
|
+
puts " --- Running all tests ---\n\n"
|
35
|
+
run_all_tests
|
36
|
+
end
|
37
|
+
|
38
|
+
# Ctrl-C
|
39
|
+
Signal.trap('INT') { abort("\n") }
|
metadata
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: dm-imap-adapter
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Nathan Herald
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-10-20 00:00:00 -04:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: DataMapper IMAP adapter
|
17
|
+
email: nathan@myobie.com
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files:
|
23
|
+
- README
|
24
|
+
files:
|
25
|
+
- .gitignore
|
26
|
+
- README
|
27
|
+
- Rakefile
|
28
|
+
- VERSION
|
29
|
+
- dm-imap-adapter.gemspec
|
30
|
+
- lib/dm-imap-adapter.rb
|
31
|
+
- lib/dm-imap-adapter/net_imap_ext.rb
|
32
|
+
- lib/dm-imap-adapter/types.rb
|
33
|
+
- spec/.bacon
|
34
|
+
- spec/dm-imap-adapter_spec.rb
|
35
|
+
- spec/spec_helper.rb
|
36
|
+
- spec/watchr.rb
|
37
|
+
has_rdoc: true
|
38
|
+
homepage: http://github.com/myobie/dm-imap-adapter
|
39
|
+
licenses: []
|
40
|
+
|
41
|
+
post_install_message:
|
42
|
+
rdoc_options:
|
43
|
+
- --charset=UTF-8
|
44
|
+
require_paths:
|
45
|
+
- lib
|
46
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
47
|
+
requirements:
|
48
|
+
- - ">="
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: "0"
|
51
|
+
version:
|
52
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: "0"
|
57
|
+
version:
|
58
|
+
requirements: []
|
59
|
+
|
60
|
+
rubyforge_project:
|
61
|
+
rubygems_version: 1.3.5
|
62
|
+
signing_key:
|
63
|
+
specification_version: 3
|
64
|
+
summary: An IMAP adapter for DM
|
65
|
+
test_files:
|
66
|
+
- spec/dm-imap-adapter_spec.rb
|
67
|
+
- spec/spec_helper.rb
|
68
|
+
- spec/watchr.rb
|