imap.rb 0.8.0 → 0.9.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG +9 -0
- data/lib/Imap/Message.rb +0 -2
- data/lib/Imap/Search.rb +12 -0
- data/lib/Imap/VERSION.rb +1 -1
- data/test/Imap/Search_test.rb +21 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a11cb6eb95eff27b2d107a0e1ad8fc727b1eabb6d2c24523ada41b3906b9d42e
|
|
4
|
+
data.tar.gz: 5a37e2eff7d0177f768aeb77182f04dd47d7686cdbd42f99814c35a140e4d270
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8a22fc1e24e9c7765d06733837547a29f581083586b0e9fdef601756ee1d61561b44689cb7dcf5ed62f3db8fc2096ac295ac8859fc765de30fd1135123a9aac6
|
|
7
|
+
data.tar.gz: 22a69f98811483319992902f342267743bbb69acac1723b1dc8067a54f9805e8965d91925a289cfd376eac22fff3d8ec333125f4f8733cf05debd9025ad38843
|
data/CHANGELOG
CHANGED
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
## 20260917
|
|
4
4
|
|
|
5
|
+
0.9.0: + all_of and ALL
|
|
6
|
+
|
|
7
|
+
1. + Imap::Search#all_of: all_of: [{text: 'invoice'}, {text: 'overdue'}] for TEXT invoice TEXT overdue. IMAP has no AND keyword, keys simply sitting side by side, but a hash cannot hold the same key twice, so two terms upon the one key could not be asked for at all: {text: 'a', text: 'b'} keeps b and drops a without a word. Each element is a criteria hash of its own, so or and not nest inside it.
|
|
8
|
+
2. ~ Imap::Search#to_imap_search_keys: + ALL_OF and + ALL, read before the operator tests beside OR and NOT, being none of the three. ALL sat in ALL_SEARCH_KEYS and in neither operator list, so {all: true} raised UnknownSearchKey. It is the key meaning every message, which is precisely what a search with no criteria has to send, so the hash could not express the simplest search there is.
|
|
9
|
+
3. ALL is a case and not a member of BOOLEAN_SEARCH_KEYS, where it appears to belong: each of those defines a method of its own name, and #all is already the alias for #message_ids which the header's own example ends upon. Adding it there redefined the alias silently, which warning = true reported and 0.4.4 turned on for this.
|
|
10
|
+
4. + 5 tests: that all_of ands a list, that it is how the one key carries several terms where a hash cannot, that or and not nest within it, that it sits beside the plain keys, and that {all: true} answers ['ALL'].
|
|
11
|
+
5. ~ Imap::VERSION: /0.8.0/0.9.0/
|
|
12
|
+
|
|
13
|
+
|
|
5
14
|
0.8.0: + Imap::Message.for, the fetch without the search, and a class a program can subclass.
|
|
6
15
|
|
|
7
16
|
1. + Imap::Message.for(imap_client, message_ids): the batched fetch on its own, .search now being the search and then this. A program which builds its own search keys had no way in: .search couples the two, so search-mail had 159 lines of its own Hit reimplementing the fetch beside it.
|
data/lib/Imap/Message.rb
CHANGED
|
@@ -29,7 +29,6 @@ class Imap
|
|
|
29
29
|
ENCODED_WORD = /=\?([^?]+)\?([BbQq])\?([^?]*)\?=/
|
|
30
30
|
|
|
31
31
|
class << self
|
|
32
|
-
|
|
33
32
|
def search(imap_client, **search_criteria)
|
|
34
33
|
self.for(imap_client, Imap::Search.new(imap_client, search_criteria).message_ids)
|
|
35
34
|
end
|
|
@@ -68,7 +67,6 @@ class Imap
|
|
|
68
67
|
rescue StandardError
|
|
69
68
|
text
|
|
70
69
|
end
|
|
71
|
-
|
|
72
70
|
end # class << self
|
|
73
71
|
|
|
74
72
|
attr_accessor :imap_client
|
data/lib/Imap/Search.rb
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
# Usage:
|
|
5
5
|
# 1. Imap::Search.new(imap_client).not.subject('Payday Loans').answered.from('noreply@example.com').all
|
|
6
6
|
# 2. Imap::Search.new(imap_client, {from: 'noreply@example.com', answered: true})
|
|
7
|
+
# 3. Imap::Search.new(imap_client, {all_of: [{text: 'invoice'}, {text: 'overdue'}]})
|
|
7
8
|
|
|
8
9
|
# Notes:
|
|
9
10
|
# 1. List of search keys taken from RFC-3501 (INTERNET MESSAGE ACCESS PROTOCOL - VERSION 4rev1), http://tools.ietf.org/html/rfc3501.
|
|
@@ -123,6 +124,10 @@ class Imap
|
|
|
123
124
|
criteria.inject([]) do |m, kv|
|
|
124
125
|
key, value = kv.first.to_s.upcase, kv.last
|
|
125
126
|
case
|
|
127
|
+
when key == 'ALL'
|
|
128
|
+
m << 'ALL'
|
|
129
|
+
when key == 'ALL_OF'
|
|
130
|
+
m << all_of(value)
|
|
126
131
|
when key == 'OR'
|
|
127
132
|
m << any_of(value)
|
|
128
133
|
when key == 'NOT'
|
|
@@ -137,6 +142,13 @@ class Imap
|
|
|
137
142
|
end.flatten
|
|
138
143
|
end
|
|
139
144
|
|
|
145
|
+
# AND has no keyword: keys sit side by side and the server ands them. A hash
|
|
146
|
+
# cannot hold the same key twice, though, so several terms upon the one key
|
|
147
|
+
# say so here: all_of: [{text: 'a'}, {text: 'b'}] for TEXT a TEXT b.
|
|
148
|
+
def all_of(criteria_hashes)
|
|
149
|
+
criteria_hashes.flat_map{|criteria_hash| Search.new(nil, criteria_hash).to_imap_search_keys}
|
|
150
|
+
end
|
|
151
|
+
|
|
140
152
|
# OR takes two keys and no more, so three criteria nest: OR OR a b c. Each
|
|
141
153
|
# is a criteria hash of its own, so either side may nest further.
|
|
142
154
|
def any_of(criteria_hashes)
|
data/lib/Imap/VERSION.rb
CHANGED
data/test/Imap/Search_test.rb
CHANGED
|
@@ -47,6 +47,27 @@ describe Imap::Search do
|
|
|
47
47
|
describe 'OR, NOT and HEADER' do
|
|
48
48
|
def keys(criteria) = Imap::Search.new(client, criteria).to_imap_search_keys
|
|
49
49
|
|
|
50
|
+
it 'answers ALL, which every key list named and neither operator list held' do
|
|
51
|
+
_(keys(all: true)).must_equal ['ALL']
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
it 'ands a list of criteria hashes for ALL_OF' do
|
|
55
|
+
_(keys(all_of: [{text: 'invoice'}, {text: 'overdue'}])).must_equal ['TEXT', 'invoice', 'TEXT', 'overdue']
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
it 'is how the one key carries more than one term, a hash holding it once' do
|
|
59
|
+
_(keys({text: 'invoice'}.merge(text: 'overdue'))).must_equal ['TEXT', 'overdue']
|
|
60
|
+
_(keys(all_of: [{text: 'invoice'}, {text: 'overdue'}])).must_equal ['TEXT', 'invoice', 'TEXT', 'overdue']
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
it 'nests OR and NOT inside ALL_OF' do
|
|
64
|
+
_(keys(all_of: [{not: {text: 'draft'}}, {or: [{from: 'a@x'}, {to: 'b@y'}]}])).must_equal ['NOT', 'TEXT', 'draft', 'OR', 'FROM', 'a@x', 'TO', 'b@y']
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
it 'sits beside the plain keys' do
|
|
68
|
+
_(keys(since: '1-Sep-2026', all_of: [{text: 'a'}, {text: 'b'}])).must_equal ['SINCE', '1-Sep-2026', 'TEXT', 'a', 'TEXT', 'b']
|
|
69
|
+
end
|
|
70
|
+
|
|
50
71
|
it 'takes two criteria hashes for OR' do
|
|
51
72
|
_(keys(or: [{from: 'a@x'}, {to: 'b@y'}])).must_equal ['OR', 'FROM', 'a@x', 'TO', 'b@y']
|
|
52
73
|
end
|