dm-imap-adapter 0.0.1
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/.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
|