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 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
@@ -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