imap_guard 0.0.1 → 0.0.2
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/.rspec +3 -0
- data/.travis.yml +17 -0
- data/Guardfile +10 -0
- data/README.md +69 -40
- data/Rakefile +8 -0
- data/imap_guard.gemspec +8 -1
- data/lib/imap_guard/guard.rb +91 -39
- data/lib/imap_guard/query.rb +12 -23
- data/lib/imap_guard/version.rb +1 -1
- data/spec/imap_guard/guard_spec.rb +197 -0
- data/spec/imap_guard/query_spec.rb +83 -0
- data/spec/spec_helper.rb +11 -0
- metadata +128 -6
data/.rspec
ADDED
data/.travis.yml
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
language: ruby
|
2
|
+
after_script:
|
3
|
+
- cane
|
4
|
+
- yard stats --list-undoc
|
5
|
+
rvm:
|
6
|
+
- 1.9.3
|
7
|
+
- 2.0.0
|
8
|
+
- ruby-head
|
9
|
+
- rbx-19mode
|
10
|
+
matrix:
|
11
|
+
allow_failures:
|
12
|
+
- rvm: ruby-head
|
13
|
+
- rvm: rbx-19mode
|
14
|
+
notifications:
|
15
|
+
email:
|
16
|
+
on_success: always
|
17
|
+
on_failure: always
|
data/Guardfile
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
# More info at https://github.com/guard/guard#readme
|
2
|
+
|
3
|
+
guard 'rspec' do
|
4
|
+
watch(%r{^spec/.+_spec\.rb$})
|
5
|
+
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
|
6
|
+
watch(%r{^lib/(.+)/(.+)\.rb$}) { |m| "spec/#{m[1]}/#{m[2]}_spec.rb" }
|
7
|
+
watch('spec/spec_helper.rb') { "spec" }
|
8
|
+
watch(%r{^spec/support/.+\.rb$}) { "spec" }
|
9
|
+
end
|
10
|
+
|
data/README.md
CHANGED
@@ -1,65 +1,94 @@
|
|
1
|
-
# ImapGuard
|
1
|
+
# ImapGuard [](https://travis-ci.org/infertux/imap_guard) [](https://gemnasium.com/infertux/imap_guard) [](https://codeclimate.com/github/infertux/imap_guard)
|
2
2
|
|
3
|
-
|
3
|
+
A guard for your IMAP mailboxes.
|
4
|
+
|
5
|
+
ImapGuard connects to your IMAP server and processes your emails.
|
6
|
+
You can finely pick them thanks to advanced search queries and Ruby blocks.
|
7
|
+
Then you can `move` or `delete` them in batch.
|
8
|
+
|
9
|
+
Of course, there is a _dry-run_ mode (i.e. read-only) available to double check what it would do.
|
10
|
+
|
11
|
+
It can be used by a disposable script to clean things up or with a cron job to keep them tidy.
|
4
12
|
|
5
13
|
## Installation
|
6
14
|
|
7
|
-
|
15
|
+
$ gem install imap_guard
|
8
16
|
|
9
|
-
|
17
|
+
## Usage
|
10
18
|
|
11
|
-
|
19
|
+
Example initialization:
|
12
20
|
|
13
|
-
|
21
|
+
```ruby
|
22
|
+
require 'imap_guard'
|
14
23
|
|
15
|
-
|
24
|
+
SETTINGS = {
|
25
|
+
host: 'mail.google.com',
|
26
|
+
port: 993,
|
27
|
+
username: 'login',
|
28
|
+
password: 'pass',
|
29
|
+
read_only: true # don't perform any modification aka dry-run mode
|
30
|
+
}
|
31
|
+
|
32
|
+
guard = IMAPGuard::Guard.new SETTINGS
|
33
|
+
guard.login # authenticate the user
|
34
|
+
guard.select 'INBOX.ops' # select the mailbox
|
35
|
+
```
|
16
36
|
|
17
|
-
|
37
|
+
IMAP search query syntax can be a bit tricky.
|
38
|
+
`IMAPGuard::Query` can help you to build queries with a simple Ruby DSL:
|
18
39
|
|
19
|
-
|
40
|
+
```ruby
|
41
|
+
base_query = IMAPGuard::Query.new.unflagged.unanswered.seen.freeze
|
42
|
+
query = base_query.dup.before(7).subject("abc").from("root")
|
43
|
+
p query #=> ["UNFLAGGED", "UNANSWERED", "SEEN", "BEFORE", "13-Mar-2013", "SUBJECT", "abc", "FROM", "root"]
|
44
|
+
guard.delete query # will delete every emails which match this query
|
45
|
+
```
|
20
46
|
|
21
|
-
|
47
|
+
Unfortunately, IMAP search queries are limited too.
|
48
|
+
For instance, the pattern passed to `subject` and `from` is a mere string.
|
49
|
+
IMAP doesn't allow advanced filtering such as regexp matching.
|
22
50
|
|
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.
|
53
|
+
However, wrapping the mail into a nice `Mail` object is slow and you should avoid to use it if you can.
|
23
54
|
|
24
55
|
```ruby
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
s = IMAPGuard::Guard.new settings
|
30
|
-
s.select 'INBOX.ops'
|
31
|
-
|
32
|
-
query = IMAPGuard::Query.new.before(7).subject("abc").from("root")
|
33
|
-
s.delete query
|
34
|
-
|
35
|
-
pattern = "monit alert -- Resource limit "
|
36
|
-
query = IMAPGuard::Query.new.subject(pattern).before(7)
|
37
|
-
s.delete query do |mail|
|
38
|
-
# the pattern given to subject() is a mere string
|
39
|
-
# pass an optional block to perform advanced filtering such as regexp matching
|
40
|
-
# the yielded mail object is an instance of the current mail providing many methods
|
41
|
-
mail.subject.start_with? pattern
|
56
|
+
guard.delete base_query.dup.before(7).subject("Logwatch for ") do |mail|
|
57
|
+
mail.subject =~ /\ALogwatch for \w \(Linux\)\Z/ and \
|
58
|
+
mail.multipart? and \
|
59
|
+
mail.parts.length == 2
|
42
60
|
end
|
61
|
+
```
|
43
62
|
|
44
|
-
|
45
|
-
s.delete query do |mail|
|
46
|
-
mail.subject =~ /\ALogwatch for \w \(Linux\)\Z/
|
47
|
-
end
|
63
|
+
Finally, you can always forge your own raw IMAP search queries (the [RFC](http://tools.ietf.org/html/rfc3501#section-6.4.4) can help in that case):
|
48
64
|
|
49
|
-
|
65
|
+
```ruby
|
50
66
|
query = 'SEEN SUBJECT "ALERT" FROM "root"'
|
51
|
-
|
52
|
-
mail.subject == "ALERT" and \
|
67
|
+
guard.delete query do |mail|
|
53
68
|
mail.body == "ALERT"
|
54
69
|
end
|
70
|
+
```
|
71
|
+
|
72
|
+
Be aware that emails won't be touched until you `expunge` or `close` the mailbox:
|
55
73
|
|
56
|
-
|
74
|
+
```ruby
|
75
|
+
guard.expunge # effectively delete emails marked as deleted
|
76
|
+
guard.close # expunge then close the connection
|
77
|
+
```
|
78
|
+
|
79
|
+
Oh, and there is a `move` method as well:
|
80
|
+
|
81
|
+
```ruby
|
82
|
+
guard.move query, 'destination_folder' do |mail|
|
83
|
+
# and it can take a filter block like `delete`
|
84
|
+
end
|
57
85
|
```
|
58
86
|
|
59
87
|
## Contributing
|
60
88
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
89
|
+
Bug reports and patches are most welcome.
|
90
|
+
|
91
|
+
## License
|
92
|
+
|
93
|
+
MIT
|
94
|
+
|
data/Rakefile
CHANGED
data/imap_guard.gemspec
CHANGED
@@ -10,7 +10,7 @@ Gem::Specification.new do |spec|
|
|
10
10
|
spec.email = ["cedric@felizard.fr"]
|
11
11
|
spec.description = %q{A guard for your IMAP server}
|
12
12
|
spec.summary = spec.description
|
13
|
-
spec.homepage = ""
|
13
|
+
spec.homepage = "https://github.com/infertux/imap_guard"
|
14
14
|
spec.license = "MIT"
|
15
15
|
|
16
16
|
spec.files = `git ls-files`.split($/)
|
@@ -19,7 +19,14 @@ Gem::Specification.new do |spec|
|
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
21
|
spec.add_dependency 'mail', '~> 2.5.3'
|
22
|
+
spec.add_dependency 'colored', '~> 1.2'
|
22
23
|
|
23
24
|
spec.add_development_dependency 'bundler', '~> 1.3'
|
24
25
|
spec.add_development_dependency 'rake'
|
26
|
+
spec.add_development_dependency 'simplecov'
|
27
|
+
spec.add_development_dependency 'rspec'
|
28
|
+
spec.add_development_dependency 'guard-rspec'
|
29
|
+
spec.add_development_dependency 'rb-inotify'
|
30
|
+
spec.add_development_dependency 'cane'
|
31
|
+
spec.add_development_dependency 'yard'
|
25
32
|
end
|
data/lib/imap_guard/guard.rb
CHANGED
@@ -1,15 +1,21 @@
|
|
1
1
|
require 'net/imap'
|
2
|
-
require 'mail'
|
3
2
|
require 'ostruct'
|
3
|
+
require 'mail'
|
4
|
+
require 'colored'
|
4
5
|
|
5
6
|
module IMAPGuard
|
6
7
|
class Guard
|
8
|
+
attr_reader :settings
|
9
|
+
|
7
10
|
def initialize settings
|
8
|
-
|
11
|
+
self.settings = settings
|
12
|
+
end
|
9
13
|
|
10
|
-
|
14
|
+
# @see http://www.ruby-doc.org/stdlib-1.9.3/libdoc/net/imap/rdoc/Net/IMAP.html#method-c-new
|
15
|
+
def login
|
11
16
|
@imap = Net::IMAP.new(@settings.host, @settings.port, true, nil, false)
|
12
17
|
@imap.login(@settings.username, @settings.password)
|
18
|
+
verbose.puts "Logged in successfully"
|
13
19
|
end
|
14
20
|
|
15
21
|
def select mailbox
|
@@ -20,51 +26,97 @@ module IMAPGuard
|
|
20
26
|
end
|
21
27
|
end
|
22
28
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
29
|
+
# @param mailbox Destination mailbox
|
30
|
+
def move query, mailbox, &filter
|
31
|
+
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
|
35
|
+
}
|
36
|
+
process query, operation, &filter
|
37
|
+
end
|
31
38
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
39
|
+
def delete query, &filter
|
40
|
+
operation = lambda { |message_id|
|
41
|
+
@imap.store(message_id, "+FLAGS", [:Deleted])
|
42
|
+
'deleted'.red
|
43
|
+
}
|
44
|
+
process query, operation, &filter
|
45
|
+
end
|
46
|
+
|
47
|
+
def expunge
|
48
|
+
@imap.expunge
|
49
|
+
end
|
50
|
+
|
51
|
+
def close
|
52
|
+
puts "Expunging deleted messages and closing mailbox..."
|
53
|
+
@imap.close
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def process query, operation
|
59
|
+
message_ids = search query
|
60
|
+
count = message_ids.size
|
61
|
+
|
62
|
+
message_ids.each_with_index do |message_id, index|
|
63
|
+
print "Processing UID #{message_id} (#{index + 1}/#{count}): "
|
64
|
+
|
65
|
+
result = true
|
41
66
|
if block_given?
|
42
|
-
|
43
|
-
|
44
|
-
result
|
45
|
-
if result
|
46
|
-
# puts "Given filter matched"
|
47
|
-
else
|
48
|
-
# puts "Given filter returned falsy"
|
49
|
-
next
|
50
|
-
end
|
67
|
+
mail = fetch_mail message_id
|
68
|
+
result = yield(mail)
|
69
|
+
verbose.print "(given filter result: #{result.inspect}) "
|
51
70
|
end
|
52
71
|
|
53
|
-
|
54
|
-
|
55
|
-
if mail
|
56
|
-
puts ": #{mail.subject} (#{mail.body.to_s.length})"
|
72
|
+
if result
|
73
|
+
puts operation.call(message_id)
|
57
74
|
else
|
58
|
-
puts
|
75
|
+
puts "ignored".yellow
|
59
76
|
end
|
60
|
-
@imap.store(message_id, "+FLAGS", [:Deleted])
|
61
77
|
end
|
62
78
|
end
|
63
79
|
|
64
|
-
def
|
65
|
-
|
66
|
-
|
67
|
-
|
80
|
+
def fetch_mail message_id
|
81
|
+
msg = @imap.fetch(message_id, 'RFC822')[0].attr['RFC822']
|
82
|
+
Mail.read_from_string msg
|
83
|
+
end
|
84
|
+
|
85
|
+
def search query
|
86
|
+
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"
|
88
|
+
end
|
89
|
+
|
90
|
+
messages = @imap.search query
|
91
|
+
puts "Query: #{query.inspect}: #{messages.count} results".cyan
|
92
|
+
|
93
|
+
messages
|
94
|
+
end
|
95
|
+
|
96
|
+
def verbose
|
97
|
+
@verbose ||= if @settings.verbose
|
98
|
+
$stdout
|
99
|
+
else
|
100
|
+
# anonymous null object
|
101
|
+
Class.new do
|
102
|
+
def method_missing(*args, &block)
|
103
|
+
nil
|
104
|
+
end
|
105
|
+
end.new
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def settings= settings
|
110
|
+
required = %w(host port username password).map!(&:to_sym)
|
111
|
+
missing = required - settings.keys
|
112
|
+
raise ArgumentError, "Missing settings: #{missing}" unless missing.empty?
|
113
|
+
|
114
|
+
optional = %w(read_only verbose).map!(&:to_sym)
|
115
|
+
unknown = settings.keys - required - optional
|
116
|
+
raise ArgumentError, "Unknown settings: #{unknown}" unless unknown.empty?
|
117
|
+
|
118
|
+
@settings = OpenStruct.new(settings).freeze
|
119
|
+
puts "DRY-RUN MODE ENABLED".yellow.bold.reversed if @settings.read_only
|
68
120
|
end
|
69
121
|
end
|
70
122
|
end
|
data/lib/imap_guard/query.rb
CHANGED
@@ -1,49 +1,38 @@
|
|
1
1
|
module IMAPGuard
|
2
|
-
class Query
|
3
|
-
def initialize
|
4
|
-
@criteria = []
|
5
|
-
seen.unanswered.unflagged
|
6
|
-
end
|
7
|
-
|
8
|
-
def to_s
|
9
|
-
@criteria.join ' '
|
10
|
-
end
|
11
|
-
|
2
|
+
class Query < Array
|
12
3
|
def seen
|
13
|
-
|
14
|
-
self
|
4
|
+
self << 'SEEN'
|
15
5
|
end
|
16
6
|
|
17
7
|
def unanswered
|
18
|
-
|
19
|
-
self
|
8
|
+
self << 'UNANSWERED'
|
20
9
|
end
|
21
10
|
|
22
11
|
def unflagged
|
23
|
-
|
24
|
-
self
|
12
|
+
self << 'UNFLAGGED'
|
25
13
|
end
|
26
14
|
|
27
15
|
def subject string
|
28
|
-
|
29
|
-
self
|
16
|
+
self << 'SUBJECT' << string
|
30
17
|
end
|
31
18
|
|
32
19
|
def from string
|
33
|
-
|
34
|
-
self
|
20
|
+
self << 'FROM' << string
|
35
21
|
end
|
36
22
|
|
37
23
|
def before date
|
38
24
|
case date
|
25
|
+
when String
|
26
|
+
# noop, uses it as is
|
39
27
|
when Fixnum
|
40
28
|
date = (Date.today - date).strftime '%e-%b-%Y'
|
41
29
|
when Date
|
42
|
-
date =
|
30
|
+
date = date.strftime '%e-%b-%Y'
|
31
|
+
else
|
32
|
+
raise ArgumentError, "#{date.inspect} is invalid"
|
43
33
|
end
|
44
34
|
|
45
|
-
|
46
|
-
self
|
35
|
+
self << 'BEFORE' << date
|
47
36
|
end
|
48
37
|
end
|
49
38
|
end
|
data/lib/imap_guard/version.rb
CHANGED
@@ -0,0 +1,197 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module IMAPGuard
|
4
|
+
describe Guard do
|
5
|
+
before do
|
6
|
+
$stdout = StringIO.new # mute stdout - comment to debug
|
7
|
+
end
|
8
|
+
|
9
|
+
let(:settings) do
|
10
|
+
{
|
11
|
+
host: 'localhost',
|
12
|
+
port: 993,
|
13
|
+
username: 'bob',
|
14
|
+
password: 'PASS',
|
15
|
+
}
|
16
|
+
end
|
17
|
+
|
18
|
+
let(:imap) {
|
19
|
+
double('Net::IMAP', search: [7, 28])
|
20
|
+
}
|
21
|
+
|
22
|
+
def guard_instance custom_settings = {}
|
23
|
+
guard = Guard.new(settings.merge(custom_settings))
|
24
|
+
guard.instance_variable_set(:@imap, imap)
|
25
|
+
guard.stub(:fetch_mail)
|
26
|
+
guard
|
27
|
+
end
|
28
|
+
|
29
|
+
describe "#select" do
|
30
|
+
context "with settings.read_only = true" do
|
31
|
+
let(:guard) { guard_instance(read_only: true) }
|
32
|
+
|
33
|
+
it "opens the mailbox in read-only" do
|
34
|
+
imap.should_receive(:examine)
|
35
|
+
guard.select nil
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
context "with settings.read_only = false" do
|
40
|
+
let(:guard) { guard_instance(read_only: false) }
|
41
|
+
|
42
|
+
it "opens the mailbox in read-write" do
|
43
|
+
imap.should_receive(:select)
|
44
|
+
guard.select nil
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
describe "#search" do
|
50
|
+
before do
|
51
|
+
imap.should_receive(:search) do |arg|
|
52
|
+
[13, 37] if [['ALL'], 'ALL'].include? arg
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
it "accepts arrays" do
|
57
|
+
expect {
|
58
|
+
guard_instance.send(:search, ['ALL'])
|
59
|
+
}.to_not raise_error
|
60
|
+
end
|
61
|
+
|
62
|
+
it "accepts strings" do
|
63
|
+
expect {
|
64
|
+
guard_instance.send(:search, 'ALL')
|
65
|
+
}.to_not raise_error
|
66
|
+
end
|
67
|
+
|
68
|
+
it "returns results" do
|
69
|
+
messages = guard_instance.send(:search, 'ALL')
|
70
|
+
messages.should eq [13, 37]
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
describe "#process" do
|
75
|
+
let(:guard) { guard_instance(verbose: true) }
|
76
|
+
let(:opeartion) { ->(id) {} }
|
77
|
+
|
78
|
+
context "without a filter block" do
|
79
|
+
it "does perform the operation" do
|
80
|
+
opeartion.should_receive(:call).with(7)
|
81
|
+
opeartion.should_receive(:call).with(28)
|
82
|
+
|
83
|
+
guard.send(:process, 'ALL', opeartion)
|
84
|
+
end
|
85
|
+
|
86
|
+
it "does not execute the filter block" do
|
87
|
+
guard.should_not_receive(:fetch_mail)
|
88
|
+
|
89
|
+
guard.send(:process, 'ALL', opeartion)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
context "with a filter block" do
|
94
|
+
it "executes the filter block" do
|
95
|
+
guard.should_receive(:fetch_mail).twice
|
96
|
+
|
97
|
+
guard.send(:process, 'ALL', opeartion) { }
|
98
|
+
end
|
99
|
+
|
100
|
+
context "returning false" do
|
101
|
+
it "does not perform the operation" do
|
102
|
+
opeartion.should_not_receive(:call)
|
103
|
+
|
104
|
+
guard.send(:process, 'ALL', opeartion) { false }
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
context "returning true" do
|
109
|
+
it "does perform the operation" do
|
110
|
+
opeartion.should_receive(:call).twice
|
111
|
+
|
112
|
+
guard.send(:process, 'ALL', opeartion) { true }
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
describe "#move" do
|
119
|
+
it "copies emails before adding the :Deleted flag" do
|
120
|
+
imap.should_receive(:search)
|
121
|
+
imap.should_receive(:copy).with(7, 'destination').ordered
|
122
|
+
imap.should_receive(:store).with(7, '+FLAGS', [:Deleted]).ordered
|
123
|
+
imap.should_receive(:copy).with(28, 'destination').ordered
|
124
|
+
imap.should_receive(:store).with(28, '+FLAGS', [:Deleted]).ordered
|
125
|
+
|
126
|
+
guard_instance.move 'ALL', 'destination'
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
describe "#delete" do
|
131
|
+
it "adds the :Deleted flag" do
|
132
|
+
imap.should_receive(:search)
|
133
|
+
imap.should_receive(:store).with(7, '+FLAGS', [:Deleted])
|
134
|
+
imap.should_receive(:store).with(28, '+FLAGS', [:Deleted])
|
135
|
+
|
136
|
+
guard_instance.delete 'ALL'
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
describe "#expunge" do
|
141
|
+
it "expunges the folder" do
|
142
|
+
imap.should_receive(:expunge)
|
143
|
+
guard_instance.expunge
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
describe "#close" do
|
148
|
+
it "closes the IMAP connection" do
|
149
|
+
imap.should_receive(:close)
|
150
|
+
guard_instance.close
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
describe "#verbose" do
|
155
|
+
context "with settings.verbose = true" do
|
156
|
+
let(:guard) { guard_instance(verbose: true) }
|
157
|
+
|
158
|
+
it "does output to $stdout" do
|
159
|
+
$stdout.should_receive(:write).with("ham")
|
160
|
+
guard.send(:verbose).print "ham"
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
context "with settings.verbose = false" do
|
165
|
+
let(:guard) { guard_instance(verbose: false) }
|
166
|
+
|
167
|
+
it "does not output to $stdout" do
|
168
|
+
$stdout.should_not_receive(:write)
|
169
|
+
guard.send(:verbose).print "ham"
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
describe "#settings=" do
|
175
|
+
it "freezes the settings" do
|
176
|
+
guard = Guard.new(settings)
|
177
|
+
|
178
|
+
expect {
|
179
|
+
guard.settings.host = 'example.net'
|
180
|
+
}.to raise_error(TypeError, /frozen/)
|
181
|
+
end
|
182
|
+
|
183
|
+
it "raises ArgumentError if any required key is missing" do
|
184
|
+
expect {
|
185
|
+
Guard.new({})
|
186
|
+
}.to raise_error ArgumentError, /missing/i
|
187
|
+
end
|
188
|
+
|
189
|
+
it "raises ArgumentError if any key is unknown" do
|
190
|
+
expect {
|
191
|
+
Guard.new(settings.merge(coffee: true))
|
192
|
+
}.to raise_error ArgumentError, /unknown/i
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module IMAPGuard
|
4
|
+
describe Query do
|
5
|
+
describe "#initialize" do
|
6
|
+
it { should be_empty }
|
7
|
+
end
|
8
|
+
|
9
|
+
describe "#seen" do
|
10
|
+
it "adds 'SEEN'" do
|
11
|
+
subject.seen
|
12
|
+
subject.last.should eq 'SEEN'
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
describe "#unanswered" do
|
17
|
+
it "adds 'UNANSWERED'" do
|
18
|
+
subject.unanswered
|
19
|
+
subject.last.should eq 'UNANSWERED'
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe "#unflagged" do
|
24
|
+
it "adds 'UNFLAGGED'" do
|
25
|
+
subject.unflagged
|
26
|
+
subject.last.should eq 'UNFLAGGED'
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe "#subject" do
|
31
|
+
it "adds the search value" do
|
32
|
+
subject.subject("Hey you")
|
33
|
+
subject.last.should eq "Hey you"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe "#from" do
|
38
|
+
it "adds the search value" do
|
39
|
+
subject.from("root@example.net")
|
40
|
+
subject.last.should eq "root@example.net"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
describe "#before" do
|
45
|
+
context "when I pass 'nil' as an argument" do
|
46
|
+
it "raises" do
|
47
|
+
expect {
|
48
|
+
subject.before(nil)
|
49
|
+
}.to raise_error ArgumentError
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
context "when I pass '1' as an argument" do
|
54
|
+
it "returns yesterday" do
|
55
|
+
subject.before(1)
|
56
|
+
Date.parse(subject.last).should eq Date.today.prev_day
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
context "when I pass an integer" do
|
61
|
+
it "uses it as a negative offset in days" do
|
62
|
+
subject.before(3)
|
63
|
+
(Date.today - Date.parse(subject.last)).should eq 3
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
context "when I pass '18-Mar-2013' as an argument" do
|
68
|
+
it "uses it as is" do
|
69
|
+
subject.before('18-Mar-2013')
|
70
|
+
subject.last.should eq '18-Mar-2013'
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
context "when I pass an instance of Date as an argument" do
|
75
|
+
it "extracts the date" do
|
76
|
+
subject.before(Date.today)
|
77
|
+
Date.parse(subject.last).should eq Date.today
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
data/spec/spec_helper.rb
ADDED
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.
|
4
|
+
version: 0.0.2
|
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-
|
12
|
+
date: 2013-03-24 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: mail
|
@@ -27,6 +27,22 @@ dependencies:
|
|
27
27
|
- - ~>
|
28
28
|
- !ruby/object:Gem::Version
|
29
29
|
version: 2.5.3
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: colored
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ~>
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '1.2'
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '1.2'
|
30
46
|
- !ruby/object:Gem::Dependency
|
31
47
|
name: bundler
|
32
48
|
requirement: !ruby/object:Gem::Requirement
|
@@ -59,6 +75,102 @@ dependencies:
|
|
59
75
|
- - ! '>='
|
60
76
|
- !ruby/object:Gem::Version
|
61
77
|
version: '0'
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: simplecov
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
type: :development
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: rspec
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ! '>='
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
type: :development
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ! '>='
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
110
|
+
- !ruby/object:Gem::Dependency
|
111
|
+
name: guard-rspec
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
113
|
+
none: false
|
114
|
+
requirements:
|
115
|
+
- - ! '>='
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
none: false
|
122
|
+
requirements:
|
123
|
+
- - ! '>='
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '0'
|
126
|
+
- !ruby/object:Gem::Dependency
|
127
|
+
name: rb-inotify
|
128
|
+
requirement: !ruby/object:Gem::Requirement
|
129
|
+
none: false
|
130
|
+
requirements:
|
131
|
+
- - ! '>='
|
132
|
+
- !ruby/object:Gem::Version
|
133
|
+
version: '0'
|
134
|
+
type: :development
|
135
|
+
prerelease: false
|
136
|
+
version_requirements: !ruby/object:Gem::Requirement
|
137
|
+
none: false
|
138
|
+
requirements:
|
139
|
+
- - ! '>='
|
140
|
+
- !ruby/object:Gem::Version
|
141
|
+
version: '0'
|
142
|
+
- !ruby/object:Gem::Dependency
|
143
|
+
name: cane
|
144
|
+
requirement: !ruby/object:Gem::Requirement
|
145
|
+
none: false
|
146
|
+
requirements:
|
147
|
+
- - ! '>='
|
148
|
+
- !ruby/object:Gem::Version
|
149
|
+
version: '0'
|
150
|
+
type: :development
|
151
|
+
prerelease: false
|
152
|
+
version_requirements: !ruby/object:Gem::Requirement
|
153
|
+
none: false
|
154
|
+
requirements:
|
155
|
+
- - ! '>='
|
156
|
+
- !ruby/object:Gem::Version
|
157
|
+
version: '0'
|
158
|
+
- !ruby/object:Gem::Dependency
|
159
|
+
name: yard
|
160
|
+
requirement: !ruby/object:Gem::Requirement
|
161
|
+
none: false
|
162
|
+
requirements:
|
163
|
+
- - ! '>='
|
164
|
+
- !ruby/object:Gem::Version
|
165
|
+
version: '0'
|
166
|
+
type: :development
|
167
|
+
prerelease: false
|
168
|
+
version_requirements: !ruby/object:Gem::Requirement
|
169
|
+
none: false
|
170
|
+
requirements:
|
171
|
+
- - ! '>='
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: '0'
|
62
174
|
description: A guard for your IMAP server
|
63
175
|
email:
|
64
176
|
- cedric@felizard.fr
|
@@ -67,8 +179,11 @@ extensions: []
|
|
67
179
|
extra_rdoc_files: []
|
68
180
|
files:
|
69
181
|
- .gitignore
|
182
|
+
- .rspec
|
70
183
|
- .ruby-version
|
184
|
+
- .travis.yml
|
71
185
|
- Gemfile
|
186
|
+
- Guardfile
|
72
187
|
- LICENSE.txt
|
73
188
|
- README.md
|
74
189
|
- Rakefile
|
@@ -77,7 +192,10 @@ files:
|
|
77
192
|
- lib/imap_guard/guard.rb
|
78
193
|
- lib/imap_guard/query.rb
|
79
194
|
- lib/imap_guard/version.rb
|
80
|
-
|
195
|
+
- spec/imap_guard/guard_spec.rb
|
196
|
+
- spec/imap_guard/query_spec.rb
|
197
|
+
- spec/spec_helper.rb
|
198
|
+
homepage: https://github.com/infertux/imap_guard
|
81
199
|
licenses:
|
82
200
|
- MIT
|
83
201
|
post_install_message:
|
@@ -92,7 +210,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
92
210
|
version: '0'
|
93
211
|
segments:
|
94
212
|
- 0
|
95
|
-
hash: -
|
213
|
+
hash: -500228931
|
96
214
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
97
215
|
none: false
|
98
216
|
requirements:
|
@@ -101,11 +219,15 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
101
219
|
version: '0'
|
102
220
|
segments:
|
103
221
|
- 0
|
104
|
-
hash: -
|
222
|
+
hash: -500228931
|
105
223
|
requirements: []
|
106
224
|
rubyforge_project:
|
107
225
|
rubygems_version: 1.8.25
|
108
226
|
signing_key:
|
109
227
|
specification_version: 3
|
110
228
|
summary: A guard for your IMAP server
|
111
|
-
test_files:
|
229
|
+
test_files:
|
230
|
+
- spec/imap_guard/guard_spec.rb
|
231
|
+
- spec/imap_guard/query_spec.rb
|
232
|
+
- spec/spec_helper.rb
|
233
|
+
has_rdoc:
|