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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 45cf03dcb336e9795a5ef5339e6ac0c6e3b2f5e79014f3462684f39f79aae98f
4
- data.tar.gz: e204d83b456b966f0f9a9c3a55acfb492564da65a2b2be22d8c27326104ba755
3
+ metadata.gz: a11cb6eb95eff27b2d107a0e1ad8fc727b1eabb6d2c24523ada41b3906b9d42e
4
+ data.tar.gz: 5a37e2eff7d0177f768aeb77182f04dd47d7686cdbd42f99814c35a140e4d270
5
5
  SHA512:
6
- metadata.gz: 20938befdd15cc9da7f5679b0043facbdbc0d3a69b04f93a0100c43b4314b6832ecfed375484d1429221ee60703dfc94ac3abdd1f4ff40060ed528828187fcf4
7
- data.tar.gz: e467e3aed0b2b09cc86e175a1b3f940e37922232e4efb97998c5dc7a959b5a3f9b8f012bb1a8ff6505d7dff83f488e418c5e7d8670fe5b4db7333991ff683d77
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
@@ -1,3 +1,3 @@
1
1
  class Imap
2
- VERSION = '0.8.0'
2
+ VERSION = '0.9.0'
3
3
  end
@@ -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
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: imap.rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.0
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - thoran