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