imap_guard 0.0.2 → 0.0.3

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/README.md CHANGED
@@ -49,7 +49,7 @@ For instance, the pattern passed to `subject` and `from` is a mere string.
49
49
  IMAP doesn't allow advanced filtering such as regexp matching.
50
50
 
51
51
  To do so, you can pass an optional block to `delete`.
52
- The yielded object is a [Mail](https://github.com/mikel/mail) instance of the current mail providing many methods.
52
+ The yielded object is a [Mail] instance of the current mail providing many methods.
53
53
  However, wrapping the mail into a nice `Mail` object is slow and you should avoid to use it if you can.
54
54
 
55
55
  ```ruby
@@ -84,6 +84,36 @@ guard.move query, 'destination_folder' do |mail|
84
84
  end
85
85
  ```
86
86
 
87
+ ### Advanced features
88
+
89
+ #### Mailbox list
90
+
91
+ You can list all mailboxes:
92
+
93
+ ```ruby
94
+ p guard.list
95
+ ```
96
+
97
+ #### Selected mailbox
98
+
99
+ You can output the currently selected mailbox:
100
+
101
+ ```ruby
102
+ p guard.mailbox # nil if none has been selected
103
+ ```
104
+
105
+ #### Debug block
106
+
107
+ You can pass a block which will be yielded for each matched email:
108
+
109
+ ```ruby
110
+ # Print out the subject for each email
111
+ guard.debug = ->(mail) { print "#{mail.subject}: " }
112
+ ```
113
+
114
+ You can think of it as Ruby's [Object#tap](http://ruby-doc.org/core-2.0/Object.html#method-i-tap) method.
115
+ Note this is slow since it needs to fetch the whole email to return a [Mail] object.
116
+
87
117
  ## Contributing
88
118
 
89
119
  Bug reports and patches are most welcome.
@@ -92,3 +122,6 @@ Bug reports and patches are most welcome.
92
122
 
93
123
  MIT
94
124
 
125
+
126
+ [Mail]: https://github.com/mikel/mail
127
+
@@ -4,53 +4,98 @@ require 'mail'
4
4
  require 'colored'
5
5
 
6
6
  module IMAPGuard
7
+ # Guard allows you to process your mailboxes.
7
8
  class Guard
9
+ # [Proc] Matched emails are passed to this debug lambda if present
10
+ attr_accessor :debug
11
+
12
+ # @return [OpenStruct] IMAPGuard settings
8
13
  attr_reader :settings
9
14
 
15
+ # @return [String] Currently selected mailbox
16
+ attr_reader :mailbox
17
+
10
18
  def initialize settings
11
19
  self.settings = settings
12
20
  end
13
21
 
22
+ # Authenticates to the given IMAP server
14
23
  # @see http://www.ruby-doc.org/stdlib-1.9.3/libdoc/net/imap/rdoc/Net/IMAP.html#method-c-new
24
+ # @return [void]
15
25
  def login
16
26
  @imap = Net::IMAP.new(@settings.host, @settings.port, true, nil, false)
17
27
  @imap.login(@settings.username, @settings.password)
18
28
  verbose.puts "Logged in successfully"
19
29
  end
20
30
 
31
+ # Selects a mailbox (folder)
32
+ # @see {settings.read_only}
33
+ # @return [void]
21
34
  def select mailbox
22
35
  if @settings.read_only
23
36
  @imap.examine(mailbox) # open in read-only
24
37
  else
25
38
  @imap.select(mailbox) # open in read-write
26
39
  end
40
+ @mailbox = mailbox
27
41
  end
28
42
 
43
+ # Moves messages matching the query and filter block
44
+ # @param query IMAP query
29
45
  # @param mailbox Destination mailbox
46
+ # @param filter Optional filter block
47
+ # @return [void]
30
48
  def move query, mailbox, &filter
31
49
  operation = lambda { |message_id|
32
- @imap.copy(message_id, mailbox) unless @settings.read_only
33
- @imap.store(message_id, "+FLAGS", [:Deleted])
34
- "moved to #{mailbox}".cyan
50
+ unless @settings.read_only
51
+ @imap.copy(message_id, mailbox)
52
+ @imap.store(message_id, "+FLAGS", [Net::IMAP::DELETED])
53
+ end
54
+
55
+ "moved to #{mailbox}".yellow
35
56
  }
36
57
  process query, operation, &filter
37
58
  end
38
59
 
60
+ # Deletes messages matching the query and filter block
61
+ # @param query IMAP query
62
+ # @param filter Optional filter block
63
+ # @return [void]
39
64
  def delete query, &filter
40
65
  operation = lambda { |message_id|
41
- @imap.store(message_id, "+FLAGS", [:Deleted])
66
+ unless @settings.read_only
67
+ @imap.store(message_id, "+FLAGS", [Net::IMAP::DELETED])
68
+ end
69
+
42
70
  'deleted'.red
43
71
  }
44
72
  process query, operation, &filter
45
73
  end
46
74
 
75
+ # @return [Array] Sorted list of all mailboxes
76
+ def list
77
+ @imap.list("", "*").map(&:name).sort
78
+ end
79
+
80
+ # Sends a EXPUNGE command to permanently remove from the currently selected
81
+ # mailbox all messages that have the Deleted flag set.
82
+ # @return [void]
47
83
  def expunge
48
- @imap.expunge
84
+ @imap.expunge unless @settings.read_only
49
85
  end
50
86
 
87
+ # Sends a CLOSE command to close the currently selected mailbox. The CLOSE
88
+ # command permanently removes from the mailbox all messages that have the
89
+ # Deleted flag set.
90
+ # @return [void]
51
91
  def close
52
- puts "Expunging deleted messages and closing mailbox..."
53
- @imap.close
92
+ @imap.close unless @settings.read_only
93
+ end
94
+
95
+ # Disconnects from the server.
96
+ # @return [void]
97
+ def disconnect
98
+ @imap.disconnect
54
99
  end
55
100
 
56
101
  private
@@ -60,35 +105,44 @@ module IMAPGuard
60
105
  count = message_ids.size
61
106
 
62
107
  message_ids.each_with_index do |message_id, index|
63
- print "Processing UID #{message_id} (#{index + 1}/#{count}): "
108
+ print "Processing UID #{message_id} (#{index.succ}/#{count}): "
64
109
 
65
110
  result = true
66
- if block_given?
111
+ if block_given? or debug
67
112
  mail = fetch_mail message_id
68
- result = yield(mail)
69
- verbose.print "(given filter result: #{result.inspect}) "
113
+
114
+ debug.call(mail) if debug
115
+
116
+ if block_given?
117
+ result = yield(mail)
118
+ verbose.print "(given filter result: #{result.inspect}) "
119
+ end
70
120
  end
71
121
 
72
122
  if result
73
123
  puts operation.call(message_id)
74
124
  else
75
- puts "ignored".yellow
125
+ puts "ignored".green
76
126
  end
77
127
  end
128
+
129
+ ensure
130
+ expunge
78
131
  end
79
132
 
133
+ # @note We use "BODY.PEEK[]" to avoid setting the \Seen flag.
80
134
  def fetch_mail message_id
81
- msg = @imap.fetch(message_id, 'RFC822')[0].attr['RFC822']
135
+ msg = @imap.fetch(message_id, 'BODY.PEEK[]').first.attr['BODY[]']
82
136
  Mail.read_from_string msg
83
137
  end
84
138
 
85
139
  def search query
86
140
  unless [Array, String].any? { |type| query.is_a? type }
87
- raise ArgumentError, "query must be either a string holding the entire search string, or a single-dimension array of search keywords and arguments"
141
+ raise TypeError, "query must be either a string holding the entire search string, or a single-dimension array of search keywords and arguments"
88
142
  end
89
143
 
90
144
  messages = @imap.search query
91
- puts "Query: #{query.inspect}: #{messages.count} results".cyan
145
+ puts "Query on #{mailbox}: #{query.inspect}: #{messages.count} results".cyan
92
146
 
93
147
  messages
94
148
  end
@@ -99,7 +153,7 @@ module IMAPGuard
99
153
  else
100
154
  # anonymous null object
101
155
  Class.new do
102
- def method_missing(*args, &block)
156
+ def method_missing(*)
103
157
  nil
104
158
  end
105
159
  end.new
@@ -1,25 +1,60 @@
1
1
  module IMAPGuard
2
+ # Query is a neat DSL to help you generate IMAP search queries.
2
3
  class Query < Array
4
+ # Messages that have the \Seen flag set.
5
+ # @return [Query] self
3
6
  def seen
4
7
  self << 'SEEN'
5
8
  end
6
9
 
10
+ # Messages that do not have the \Answered flag set.
11
+ # @return [Query] self
7
12
  def unanswered
8
13
  self << 'UNANSWERED'
9
14
  end
10
15
 
16
+ # Messages that do not have the \Flagged flag set.
17
+ # @return [Query] self
11
18
  def unflagged
12
19
  self << 'UNFLAGGED'
13
20
  end
14
21
 
22
+ # Messages that match either search key.
23
+ # @note Reverse polish notation is expected,
24
+ # i.e. OR <search-key1> <search-key2>
25
+ # @return [Query] self
26
+ def or
27
+ self << 'OR'
28
+ end
29
+
30
+ # Messages that contain the specified string in the envelope
31
+ # structure's SUBJECT field.
32
+ # @return [Query] self
15
33
  def subject string
16
34
  self << 'SUBJECT' << string
17
35
  end
18
36
 
37
+ # Messages that contain the specified string in the envelope
38
+ # structure's FROM field.
39
+ # @return [Query] self
19
40
  def from string
20
41
  self << 'FROM' << string
21
42
  end
22
43
 
44
+ # Messages that contain the specified string in the envelope
45
+ # structure's TO field.
46
+ # @return [Query] self
47
+ def to string
48
+ self << 'TO' << string
49
+ end
50
+
51
+ # Messages whose internal date (disregarding time and timezone)
52
+ # is earlier than the specified date.
53
+ # @param date Depending of its type:
54
+ # - String: uses it as is
55
+ # - Fixnum: _n_ days before today
56
+ # - Date: uses this date
57
+ # @return [Query] self
23
58
  def before date
24
59
  case date
25
60
  when String
@@ -1,3 +1,4 @@
1
1
  module ImapGuard
2
- VERSION = "0.0.2"
2
+ # [String] ImapGuard version
3
+ VERSION = "0.0.3"
3
4
  end
data/lib/imap_guard.rb CHANGED
@@ -2,5 +2,6 @@ require "imap_guard/version"
2
2
  require "imap_guard/guard"
3
3
  require "imap_guard/query"
4
4
 
5
+ # ImapGuard default module
5
6
  module ImapGuard
6
7
  end
@@ -6,6 +6,10 @@ module IMAPGuard
6
6
  $stdout = StringIO.new # mute stdout - comment to debug
7
7
  end
8
8
 
9
+ after do
10
+ $stdout = STDOUT
11
+ end
12
+
9
13
  let(:settings) do
10
14
  {
11
15
  host: 'localhost',
@@ -16,7 +20,7 @@ module IMAPGuard
16
20
  end
17
21
 
18
22
  let(:imap) {
19
- double('Net::IMAP', search: [7, 28])
23
+ double('Net::IMAP', search: [7, 28], expunge: nil, select: nil, list: [])
20
24
  }
21
25
 
22
26
  def guard_instance custom_settings = {}
@@ -46,6 +50,26 @@ module IMAPGuard
46
50
  end
47
51
  end
48
52
 
53
+ describe "#mailbox" do
54
+ it "returns nil when no mailbox has been selected" do
55
+ guard_instance.mailbox.should be_nil
56
+ end
57
+
58
+ it "returns the currently selected mailbox" do
59
+ guard = guard_instance
60
+
61
+ guard.select 'Sent'
62
+ guard.mailbox.should eq 'Sent'
63
+ end
64
+ end
65
+
66
+ describe "#list" do
67
+ it "returns the list of mailboxes" do
68
+ imap.should_receive(:list)
69
+ guard_instance.list.should eq []
70
+ end
71
+ end
72
+
49
73
  describe "#search" do
50
74
  before do
51
75
  imap.should_receive(:search) do |arg|
@@ -88,6 +112,16 @@ module IMAPGuard
88
112
 
89
113
  guard.send(:process, 'ALL', opeartion)
90
114
  end
115
+
116
+ context "with a debug proc" do
117
+ it "calls the proc" do
118
+ block = ->(mail) {}
119
+ guard.debug = block
120
+ block.should_receive(:call).twice
121
+
122
+ guard.send(:process, 'ALL', opeartion)
123
+ end
124
+ end
91
125
  end
92
126
 
93
127
  context "with a filter block" do
@@ -151,6 +185,13 @@ module IMAPGuard
151
185
  end
152
186
  end
153
187
 
188
+ describe "#disconnect" do
189
+ it "disconnects from the server" do
190
+ imap.should_receive(:disconnect)
191
+ guard_instance.disconnect
192
+ end
193
+ end
194
+
154
195
  describe "#verbose" do
155
196
  context "with settings.verbose = true" do
156
197
  let(:guard) { guard_instance(verbose: true) }
@@ -27,6 +27,13 @@ module IMAPGuard
27
27
  end
28
28
  end
29
29
 
30
+ describe "#or" do
31
+ it "adds 'OR'" do
32
+ subject.or
33
+ subject.last.should eq 'OR'
34
+ end
35
+ end
36
+
30
37
  describe "#subject" do
31
38
  it "adds the search value" do
32
39
  subject.subject("Hey you")
@@ -41,6 +48,13 @@ module IMAPGuard
41
48
  end
42
49
  end
43
50
 
51
+ describe "#to" do
52
+ it "adds the search value" do
53
+ subject.to("root@example.net")
54
+ subject.last.should eq "root@example.net"
55
+ end
56
+ end
57
+
44
58
  describe "#before" do
45
59
  context "when I pass 'nil' as an argument" do
46
60
  it "raises" do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: imap_guard
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-03-24 00:00:00.000000000 Z
12
+ date: 2013-04-04 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: mail
@@ -210,7 +210,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
210
210
  version: '0'
211
211
  segments:
212
212
  - 0
213
- hash: -500228931
213
+ hash: 903698273
214
214
  required_rubygems_version: !ruby/object:Gem::Requirement
215
215
  none: false
216
216
  requirements:
@@ -219,7 +219,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
219
219
  version: '0'
220
220
  segments:
221
221
  - 0
222
- hash: -500228931
222
+ hash: 903698273
223
223
  requirements: []
224
224
  rubyforge_project:
225
225
  rubygems_version: 1.8.25