imap 0.10.0 → 0.12.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: a5a7c49aedce3c0b0aaf2ab4c707f404c91dcdd0fbda9abc68107008816ad2ce
4
- data.tar.gz: 3352fdcb214ee0a7bf631e67c068f19fac38a3349b1f0f54cb9286b7652e834f
3
+ metadata.gz: cf1e1e584ce9151c1729d359ab2ba2ac68b75c3d06a3fd15ef7512defa491c12
4
+ data.tar.gz: 0f07df5b9ae03d1421e5a243655fdfe56237ee2c1a9fc7597cacc3599f6db0a2
5
5
  SHA512:
6
- metadata.gz: e843366a424bd2544e527b2b6ea8368397bc36263bc3eb648bbe6c9dbc8ba052a4f0825c6cf4f626d82eb5f179f25954502e2fcd040cb8dcb9bd5fdffca40846
7
- data.tar.gz: 6b17ea64de5bd24e4188ce82e56022c62f2ad0efa2bcd82682a673435e997b1902617eabaa94168c62185370da7c15901d38ec6101ff2fb911847795dcedefc4
6
+ metadata.gz: 696700ed822fd7d2af94fa66d07dd81dced92892a8437d64c53ac4ed94e27df3a9846374cb148deee2225bcc8250745e394bb151ed2029839bfbbc68a250faf0
7
+ data.tar.gz: 5c3786b4fa23995246292616675a0d2aa394c0827cb2114caee990de8992d971506f44fee5acc533ca17960b7504b239f0fcf52657f5414ad4ec9d2de1b8bf6f
data/CHANGELOG CHANGED
@@ -1,7 +1,32 @@
1
1
  # imap/CHANGELOG
2
2
 
3
+ ## 20261014
4
+
5
+ 0.12.0: ~ Imap::Message: + #mailbox, and a select before every fetch and store. A sequence number is valid only inside the SELECT which issued it, so a message whose body was read after another mailbox had been selected read that other mailbox instead, and said nothing about it.
6
+
7
+ 1. + Imap::Message#mailbox, taken from the client at construction, so .search and .for hand back messages which know where they came from.
8
+ 2. + Imap::Message#select_mailbox: selects back where the client has moved on since.
9
+ 3. ~ Imap::Message#fetch_data, #mark_as_seen: select_mailbox first. #body, #source and a flag store all reached the server by sequence number and none of them checked.
10
+ 4. ~ Imap::Message#initialize: + mailbox, defaulting to the client's current one, so a message built by hand is guarded too.
11
+ 5. ~ test/Imap_test.rb --> test/imap_test.rb, the file it tests being lib/imap.rb. git mv declines a destination differing only in case, so it went by way of a third name.
12
+ 6. ~ CHANGELOG: 0.4.4's second item said test_all.rb "required './Imap_test' for a file named imap_test.rb". It required './Imap_test' for test/Imap_test.rb, which matched, and the clause is struck. What remains of that item, that test_all.rb never loaded test/Imap/VERSION_test.rb, holds.
13
+ 7. + 4 tests: that a message knows its mailbox, that a fetch selects back, that a flag store selects back, and that neither selects where the client is already there. The two which select back fail with the guard removed.
14
+ 8. ~ Imap::VERSION: /0.11.0/0.12.0/
15
+
16
+
3
17
  ## 20260919
4
18
 
19
+ 0.11.0: ~ README.md: rewritten for the surface as it now stands; ~ the gemspecs: /['minitest', '~> 5.0']/'minitest'/, /['rake', '~> 13.0']/'rake'/. The README documented 0.4, five minors back, and the minitest line permitted only versions the suite no longer runs on.
20
+
21
+ 1. ~ README.md: + the searching, or, not, all_of, header and UnknownSearchKey; + the message accessors, all of them off the one fetch; + .for, .decode, #mailboxes, #source, LoginFailed, and subclassing. Every example run against the suite's mock rather than written from memory.
22
+ 2. ~ README.md: + that the gem is published under both names and that one installs one of them.
23
+ 3. ~ the gemspecs: /['minitest', '~> 5.0']/'minitest'/. The suite has run on minitest 6 since 0.4.3, which is where it gained require 'minitest/mock'; the line permitted 5.x alone.
24
+ 4. ~ the gemspecs: /['rake', '~> 13.0']/'rake'/, nothing being known to break outside it.
25
+ 5. ~ CHANGELOG: - the names of other programs and gems throughout. A reader has this repository and no other, so a comparison against software they cannot see is not evidence to them.
26
+ 6. ~ CHANGELOG: - the 6 items which recorded no change, the numbering closed up behind them.
27
+ 7. ~ Imap::VERSION: /0.10.0/0.11.0/
28
+
29
+
5
30
  0.10.0: ~ Imap.setup: + Imap::LoginFailed; ~ the gemspecs: /'~> 0.4'/'>= 0.4.5'/ upon net-imap. Imap.setup ignored the false which Imap#login returns when the server rejects the password, and handed back a client which was connected but not logged in.
6
31
 
7
32
  1. + Imap::LoginFailed
@@ -17,14 +42,13 @@
17
42
 
18
43
  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.
19
44
  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.
20
- 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.
21
- 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'].
22
- 5. ~ Imap::VERSION: /0.8.0/0.9.0/
45
+ 3. + 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'].
46
+ 4. ~ Imap::VERSION: /0.8.0/0.9.0/
23
47
 
24
48
 
25
49
  0.8.0: + Imap::Message.for, the fetch without the search, and a class a program can subclass.
26
50
 
27
- 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.
51
+ 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 coupling the two, and had to reimplement the fetch beside it.
28
52
  2. ~ Imap::Message.for: builds with new rather than Imap::Message.new, so that a subclass is handed back its own kind. A program which wants a message of its own, with a mailbox or a decoded body upon it, subclasses rather than starts again.
29
53
  3. + 4 tests: that .for builds for ids searched elsewhere, that it fetches once, that it is empty where nothing was found, and that a subclass gets itself back.
30
54
  4. ~ Imap::VERSION: /0.7.0/0.8.0/
@@ -58,29 +82,24 @@
58
82
  1. + Imap::Search#any_of: or: [{from: 'a@x'}, {to: 'b@y'}]. OR takes two keys and no more, so three or more nest, OR OR a b c, as the server reads them. Each side is a criteria hash of its own and may nest further.
59
83
  2. + Imap::Search#none_of: not: {subject: 'Payday'}. NOT takes one key, so several are negated apiece, NOT a NOT b.
60
84
  3. ~ Imap::Search#to_imap_search_keys: OR and NOT read before the operator tests, being neither general nor boolean.
61
- 4. HEADER wanted no change: an array value already flattens to ['HEADER', field, value], which nothing said and nothing tested. It is tested now.
62
- 5. + tests for each, and for OR mixed with the plain keys.
63
- 6. ~ Imap::VERSION: /0.4.9/0.5.0/
85
+ 4. + tests for each, and for OR mixed with the plain keys.
86
+ 5. ~ Imap::VERSION: /0.4.9/0.5.0/
64
87
 
65
88
 
66
89
  0.4.9: + imap.rb.gemspec, so that the gem resolves under both names.
67
90
 
68
91
  1. + imap.rb.gemspec: the same code published as imap.rb, which is how gems are named here: 21 carry the suffix and imap is one of the few which do not. rubygems has no notion of an alias, so the two are separate gems which must be kept at the same version by hand.
69
- 2. It differs from imap.gemspec in the three lines measurand and namo differ in, the heading comment naming the file, the name, and the gemspec each lists among its own files. Neither ships the other.
70
- 3. imap.rb is unregistered, so this is its first version. bitget, eodhd, measurand and webdav each had their second name join at whatever the first stood at rather than backfilling, and each did it as a patch of its own carrying nothing else.
71
- 4. gem-publish builds and pushes each in turn, gem build and gem push taking one file apiece and gem build *.gemspec building only whichever the shell lists first.
72
- 5. ~ Gemfile: gemspec name: 'imap', bundler refusing to choose between two gemspecs and stopping with "There are multiple gemspecs at ...". Every pair here which uses gemspec names one: webdav, measurand and eodhd each name the elder of their two, and namo, which carried both from its first release, names the bare one. imap is the elder here.
73
- 6. ~ Imap::VERSION: /0.4.8/0.4.9/
92
+ 2. ~ Gemfile: gemspec name: 'imap', bundler refusing to choose between two gemspecs and stopping with "There are multiple gemspecs at ...". imap is the elder of the two names.
93
+ 3. ~ Imap::VERSION: /0.4.8/0.4.9/
74
94
 
75
95
 
76
96
  0.4.8: Rewrite the CHANGELOG in the standard form.
77
97
 
78
98
  1. ~ CHANGELOG: ## before each date rather than ## [version] - date, the version and its summary beneath, a blank line between that and the first item, and the items numbered rather than dashed. No item is reworded, dropped or reordered.
79
99
  2. ~ CHANGELOG: the dates in basic form, 20260910 for 2026-09-10, as every other gem here writes them.
80
- 3. ~ CHANGELOG: + the title, # imap/CHANGELOG, as coinmarketcap.rb and release-audit have it.
100
+ 3. ~ CHANGELOG: + the title, # imap/CHANGELOG.
81
101
  4. ~ CHANGELOG: the five releases of 20260917 under one date heading, which is how the form carries more than one in a day.
82
- 5. 9 of the 12 gems here were already in this form; imap and gate.rb were not, which leaves gate.rb.
83
- 6. ~ Imap::VERSION: /0.4.7/0.4.8/
102
+ 5. ~ Imap::VERSION: /0.4.7/0.4.8/
84
103
 
85
104
  0.4.7: ~ Imap::Search: the chaining interface, which the header has documented and which has never run
86
105
 
@@ -103,24 +122,24 @@
103
122
 
104
123
  0.4.5: + Gemfile; ~ imap.gemspec: dependencies=, - spec.date, + metadata
105
124
 
106
- 1. + Gemfile: source and gemspec, as switches, eodhd and moby have it, so that the dependencies are declared once in the gemspec rather than restated here to drift. bitget and coinmarketcap restate theirs and are the minority. This was the only gem here with no Gemfile at all.
107
- 2. ~ imap.gemspec: + 'Gemfile' to spec.files, which every other gem here ships.
108
- 3. ~ imap.gemspec: dependencies= and development_dependencies= through the Gem::Specification extension, as switches, eodhd and moby have it and as bitget, coinmarketcap and this did not. The constraints survive: the splat takes a bare string or a [name, constraint] pair alike.
109
- 4. ~ imap.gemspec: - spec.date = '2025-12-20', nine months and two versions stale, and the last one left in the estate. RubyGems answers 1980-01-02 in its place, the reproducible-build epoch, which is what coinmarketcap has carried since dropping its own at 0.6.0.
110
- 5. ~ imap.gemspec: + metadata, the four uris switches and moby declare, so that the RubyGems page links to the issues, the changelog, the source and the README. master here where moby says main, each by the branch it is on.
125
+ 1. + Gemfile: source and gemspec, so that the dependencies are declared once in the gemspec rather than restated here to drift.
126
+ 2. ~ imap.gemspec: + 'Gemfile' to spec.files.
127
+ 3. ~ imap.gemspec: dependencies= and development_dependencies= through the Gem::Specification extension. The constraints survive: the splat takes a bare string or a [name, constraint] pair alike.
128
+ 4. ~ imap.gemspec: - spec.date = '2025-12-20', nine months and two versions stale. RubyGems answers 1980-01-02 in its place, the reproducible-build epoch.
129
+ 5. ~ imap.gemspec: + metadata, the four uris, so that the RubyGems page links to the issues, the changelog, the source and the README.
111
130
  6. ~ Imap::VERSION: /0.4.4/0.4.5/
112
131
 
113
132
  0.4.4: + Rakefile, - test/test_all.rb
114
133
 
115
134
  1. + Rakefile: Rake::TestTask over FileList['test/**/*_test.rb'], with libs for lib and test, and warning = true. The minimal form nine of the gems here use rather than the fuller one five use: the :spec alias and the :version task belong to the minority, and a :version task reading VERSION.rb would not have caught 0.4.1, where require 'imap' left Imap::VERSION undefined while VERSION.rb sat there defining it. rake has been a development dependency since 0.4.0 with nothing here to run.
116
- 2. - test/test_all.rb: a second entry point which the Rakefile's FileList makes redundant, and which had drifted from the files on disk in two ways: it never loaded test/Imap/VERSION_test.rb, added in 0.4.2, so it ran 42 tests where there are 44; and it required './Imap_test' for a file named imap_test.rb, which resolved only because the filesystem is case insensitive and would not have on Linux. A glob cannot drift. 14 of the 16 gems here with a Rakefile carry no such file; switches keeps spec/all.rb because its Rakefile targets it, and bitget carries the same redundancy this removes.
135
+ 2. - test/test_all.rb: a second entry point which the Rakefile's FileList makes redundant, and which had drifted from the files on disk: it never loaded test/Imap/VERSION_test.rb, added in 0.4.2, so it ran 42 tests where there are 44. A glob cannot drift.
117
136
  3. ~ test/Imap/Message_test.rb: must_match(/Mock body/) for must_match /Mock body/, the ambiguous / which warning = true reports and which is the only warning the suite raises.
118
- 4. ~ imap.gemspec: + 'Rakefile' to spec.files, as eodhd, switches and moby have.
137
+ 4. ~ imap.gemspec: + 'Rakefile' to spec.files.
119
138
  5. ~ Imap::VERSION: /0.4.3/0.4.4/
120
139
 
121
140
  0.4.3: ~ test/test_helper.rb: + require 'minitest/mock', which minitest 6 no longer brings
122
141
 
123
- 1. ~ test/test_helper.rb: + `require 'minitest/mock'`. 35 of the 42 tests call stub and not one of them could: the suite answered "42 runs, 7 assertions, 0 failures, 35 errors". minitest 5's autorun required minitest/mock and shipped it, so the suite ran until minitest 6 dropped both the require and the file; the same thing coinmarketcap 0.6.1 found.
142
+ 1. ~ test/test_helper.rb: + `require 'minitest/mock'`. 35 of the 42 tests call stub and not one of them could: the suite answered "42 runs, 7 assertions, 0 failures, 35 errors". minitest 5's autorun required minitest/mock and shipped it, so the suite ran until minitest 6 dropped both the require and the file.
124
143
  2. ~ imap.gemspec: + minitest-mock as a development dependency, which minitest ~> 5.0 carried and minitest 6 does not.
125
144
  3. ~ Imap::VERSION: /0.4.2/0.4.3/
126
145
 
@@ -130,9 +149,8 @@
130
149
  0.4.2: ~ lib/imap.rb: + require_relative './Imap/VERSION'; + test/Imap/VERSION_test.rb
131
150
 
132
151
  1. ~ lib/imap.rb: + `require_relative './Imap/VERSION'`, last among the requires. The file had been present and unreached, so `require 'imap'` left Imap::VERSION undefined and the gemspec was the only thing loading it.
133
- 2. + test/Imap/VERSION_test.rb: that Imap::VERSION is a string and that it is three numbers separated by dots. Modelled on moby's, less the example comparing against the changelog, which was unreachable here while the file was misspelt.
134
- 3. The test loads the library rather than VERSION.rb, which is the whole of its value: run against v0.4.1 it fails with "uninitialized constant Imap::VERSION". Nine libraries were in that state and not one of them carried this test.
135
- 4. ~ Imap::VERSION: /0.4.1/0.4.2/
152
+ 2. + test/Imap/VERSION_test.rb: that Imap::VERSION is a string and that it is three numbers separated by dots. Less the example comparing against the changelog, which was unreachable here while the file was misspelt.
153
+ 3. ~ Imap::VERSION: /0.4.1/0.4.2/
136
154
 
137
155
 
138
156
  ## 20260822
data/README.md CHANGED
@@ -1,11 +1,15 @@
1
1
  # imap
2
2
 
3
- A Ruby wrapper around Net::IMAP with an elegant DSL for searching, reading, and managing email.
3
+ A Ruby wrapper around Net::IMAP with a hash-based search interface and messages which answer questions rather than handing back structs.
4
4
 
5
5
  ## Features
6
6
 
7
- - Clean, hash-based search interface
8
- - Intuitive message accessors
7
+ - Hash-based search criteria, including OR, NOT and repeated keys
8
+ - Message accessors for dates, flags, size, addresses and attachments
9
+ - RFC 2047 decoding, which Net::IMAP does not do
10
+ - Attachment filenames read out of the BODYSTRUCTURE, which IMAP cannot search upon
11
+ - One fetch for a whole set of messages rather than one per attribute
12
+ - BODY.PEEK throughout, so that reading a message does not mark it read
9
13
  - No Rails dependency - works anywhere
10
14
  - Zero external dependencies beyond net-imap
11
15
 
@@ -29,38 +33,140 @@ Or install it yourself as:
29
33
  $ gem install imap
30
34
  ```
31
35
 
36
+ The gem is published under both `imap` and `imap.rb`. They are the same code; install one.
37
+
32
38
  ## Usage
39
+
40
+ ### Connecting
41
+
33
42
  ```ruby
34
43
  require 'imap'
35
44
 
36
- # Connect and authenticate
37
45
  imap_client = Imap.setup(
38
- server: 'imap.thoran.com',
39
- username: 'code@thoran.com',
46
+ server: 'imap.example.com',
47
+ username: 'someone@example.com',
40
48
  password: 'bigsecret',
41
49
  mailbox: 'INBOX' # optional/default
42
50
  )
51
+ ```
43
52
 
53
+ `Imap.setup` raises `Imap::LoginFailed` where the server rejects the password, or where only one of the username and the password is given. `Imap#login` answers true or false for a caller which would rather handle it itself.
54
+
55
+ `Imap#mailboxes` lists every mailbox which can be selected:
56
+
57
+ ```ruby
58
+ imap_client.mailboxes # => ['INBOX', 'INBOX.Archive', ...]
59
+ imap_client.mailbox = 'INBOX.Archive'
60
+ ```
61
+
62
+ ### Searching
63
+
64
+ Criteria are a hash, keyed by the IMAP search keys in lower case:
65
+
66
+ ```ruby
44
67
  messages = imap_client.search(
45
68
  from: 'noreply@example.com',
46
69
  subject: 'Payday Loans',
47
- since: '18-DEC-2025',
70
+ since: '18-Dec-2025',
48
71
  seen: false
49
72
  )
73
+ ```
74
+
75
+ `or:` takes a list of criteria hashes. IMAP's OR takes two keys, so three or more nest for you:
76
+
77
+ ```ruby
78
+ imap_client.search(or: [{from: 'a@x'}, {to: 'b@y'}, {cc: 'c@z'}])
79
+ ```
80
+
81
+ `not:` negates each key it carries:
82
+
83
+ ```ruby
84
+ imap_client.search(not: {subject: 'Payday'})
85
+ ```
86
+
87
+ `all_of:` is how one key carries several terms, a hash holding it only once:
88
+
89
+ ```ruby
90
+ imap_client.search(all_of: [{text: 'invoice'}, {text: 'overdue'}])
91
+ ```
92
+
93
+ `header:` takes the field and the value:
94
+
95
+ ```ruby
96
+ imap_client.search(header: ['List-Id', 'ruby-talk'])
97
+ ```
98
+
99
+ An unrecognised key raises `Imap::Search::UnknownSearchKey` rather than widening the search silently.
50
100
 
51
- # Access message details
101
+ Where the search keys are built elsewhere, `Imap::Message.for` does the fetch on its own:
102
+
103
+ ```ruby
104
+ Imap::Message.for(imap_client, imap_client.imap.search(['ALL']))
105
+ ```
106
+
107
+ ### Messages
108
+
109
+ A search fetches UID, FLAGS, INTERNALDATE, RFC822.SIZE, ENVELOPE and BODYSTRUCTURE for the whole set in one command, so none of these costs a round trip:
110
+
111
+ ```ruby
52
112
  messages.each do |message|
53
- puts message.subject
54
- puts message.from
55
- puts message.to
56
- puts message.body
57
- puts message.urls # Extracted URLs from body
113
+ message.uid
114
+ message.flags # => [:Seen]
115
+ message.seen?
116
+ message.size # bytes
117
+ message.received_at # Time, from INTERNALDATE
118
+ message.sent_at # Time, from the Date header
119
+ message.subject # encoded words decoded
120
+ message.from # => 'Café <billing@example.com>'
121
+ message.from_address # => 'billing@example.com'
122
+ message.from_domain # => 'example.com'
123
+ message.to # => ['user@example.com']
124
+ message.cc
125
+ message.attachment?
126
+ message.attachment_filenames # => ['résumé.pdf']
58
127
  end
128
+ ```
59
129
 
60
- # Mark messages as read
61
- messages.first.mark_as_read
130
+ The body is fetched only when it is asked for:
131
+
132
+ ```ruby
133
+ message.body # BODY[TEXT], undecoded
134
+ message.source # the whole message, headers and all
135
+ message.urls # the URLs found in the body
136
+ message.mark_as_read
137
+ ```
138
+
139
+ `#body` is the raw text part. Choosing a part out of a multipart message and undoing its transfer encoding is MIME work rather than IMAP work, so hand `#source` to the `mail` gem where that is wanted:
140
+
141
+ ```ruby
142
+ mail = Mail.read_from_string(message.source)
143
+ ```
62
144
 
63
- # Clean up
145
+ ### Decoding
146
+
147
+ `Imap::Message.decode` reads RFC 2047 encoded words, which headers carry wherever a subject or a name is not ASCII:
148
+
149
+ ```ruby
150
+ Imap::Message.decode('=?UTF-8?B?UsOpc3Vtw6k=?=') # => 'Résumé'
151
+ ```
152
+
153
+ ### Extending
154
+
155
+ `Imap::Message.search` and `.for` build with `new`, so a subclass is handed back its own kind:
156
+
157
+ ```ruby
158
+ class Message < Imap::Message
159
+ def body
160
+ @body ||= Mail.read_from_string(source).text_part.decoded
161
+ end
162
+ end
163
+
164
+ Message.for(imap_client, message_ids).first.class # => Message
165
+ ```
166
+
167
+ ### Finishing
168
+
169
+ ```ruby
64
170
  imap_client.bye
65
171
  ```
66
172
 
data/imap.gemspec CHANGED
@@ -32,9 +32,9 @@ Gem::Specification.new do |spec|
32
32
  ]
33
33
 
34
34
  spec.development_dependencies = [
35
- ['minitest', '~> 5.0'],
35
+ 'minitest',
36
36
  'minitest-mock',
37
- ['rake', '~> 13.0']
37
+ 'rake'
38
38
  ]
39
39
 
40
40
  spec.files = [
data/lib/Imap/Message.rb CHANGED
@@ -14,6 +14,7 @@
14
14
  # 10. Imap::Message.new(message_id, imap_client).received_at
15
15
  # 11. Imap::Message.new(message_id, imap_client).attachment_filenames
16
16
  # 12. Imap::Message.new(message_id, imap_client).source
17
+ # 13. Imap::Message.new(message_id, imap_client).mailbox
17
18
 
18
19
  require 'time'
19
20
 
@@ -71,6 +72,7 @@ class Imap
71
72
 
72
73
  attr_accessor :imap_client
73
74
  attr_accessor :message_id
75
+ attr_reader :mailbox
74
76
 
75
77
  def uid
76
78
  data.uid
@@ -148,16 +150,25 @@ class Imap
148
150
  end
149
151
 
150
152
  def mark_as_seen
153
+ select_mailbox
151
154
  imap_client.imap.store(@message_id, '+FLAGS', [:Seen])
152
155
  end
153
156
  alias_method :mark_as_read, :mark_as_seen
154
157
 
155
158
  private
156
159
 
157
- def initialize(message_id = nil, imap_client = nil, data = nil)
160
+ def initialize(message_id = nil, imap_client = nil, data = nil, mailbox = nil)
158
161
  @message_id = message_id
159
162
  @imap_client = imap_client
160
163
  @data = data
164
+ @mailbox = mailbox || (imap_client && imap_client.mailbox)
165
+ end
166
+
167
+ # A sequence number means nothing outside the mailbox which issued it, so
168
+ # anything fetched after another mailbox was selected has to select back.
169
+ def select_mailbox
170
+ return unless mailbox && imap_client && imap_client.mailbox != mailbox
171
+ imap_client.mailbox = mailbox
161
172
  end
162
173
 
163
174
  # The Net::IMAP::FetchData itself rather than its attr hash, since it reads
@@ -218,6 +229,7 @@ class Imap
218
229
  end
219
230
 
220
231
  def fetch_data(*fetch_attributes)
232
+ select_mailbox
221
233
  @fetch_data = imap_client.imap.fetch(message_id, [*fetch_attributes])
222
234
  end
223
235
  end
data/lib/Imap/VERSION.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  class Imap
2
- VERSION = '0.10.0'
2
+ VERSION = '0.12.0'
3
3
  end
@@ -237,6 +237,34 @@ describe Imap::Message do
237
237
  end
238
238
  end
239
239
 
240
+ describe '#mailbox' do
241
+ it 'knows the mailbox it was built in' do
242
+ _(Imap::Message.for(client, [1]).first.mailbox).must_equal 'INBOX'
243
+ end
244
+
245
+ it 'selects back before fetching, where the client has moved on since' do
246
+ message = Imap::Message.for(client, [1]).first
247
+ client.mailbox = 'Archive/2026'
248
+ message.body
249
+ _(client.mailbox).must_equal 'INBOX'
250
+ _(client.imap.selects.last).must_equal 'INBOX'
251
+ end
252
+
253
+ it 'selects back before storing a flag' do
254
+ message = Imap::Message.for(client, [1]).first
255
+ client.mailbox = 'Archive/2026'
256
+ message.mark_as_seen
257
+ _(client.imap.selects.last).must_equal 'INBOX'
258
+ end
259
+
260
+ it 'does not select where the client is already there' do
261
+ message = Imap::Message.for(client, [1]).first
262
+ before = (client.imap.selects || []).length
263
+ message.body
264
+ _((client.imap.selects || []).length).must_equal before
265
+ end
266
+ end
267
+
240
268
  describe '#urls' do
241
269
  it 'extracts http URLs from body' do
242
270
  imap_message.stub(:body, 'Check out http://example.com and https://test.com') do
data/test/test_helper.rb CHANGED
@@ -20,9 +20,12 @@ class MockIMAP
20
20
  @password = password
21
21
  end
22
22
 
23
+ attr_reader :selects
24
+
23
25
  def select(mailbox)
24
26
  @select_called = true
25
27
  @selected_mailbox = mailbox
28
+ (@selects ||= []) << mailbox
26
29
  end
27
30
 
28
31
  def search(criteria)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: imap
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.10.0
4
+ version: 0.12.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - thoran
@@ -27,16 +27,16 @@ dependencies:
27
27
  name: minitest
28
28
  requirement: !ruby/object:Gem::Requirement
29
29
  requirements:
30
- - - "~>"
30
+ - - ">="
31
31
  - !ruby/object:Gem::Version
32
- version: '5.0'
32
+ version: '0'
33
33
  type: :development
34
34
  prerelease: false
35
35
  version_requirements: !ruby/object:Gem::Requirement
36
36
  requirements:
37
- - - "~>"
37
+ - - ">="
38
38
  - !ruby/object:Gem::Version
39
- version: '5.0'
39
+ version: '0'
40
40
  - !ruby/object:Gem::Dependency
41
41
  name: minitest-mock
42
42
  requirement: !ruby/object:Gem::Requirement
@@ -55,16 +55,16 @@ dependencies:
55
55
  name: rake
56
56
  requirement: !ruby/object:Gem::Requirement
57
57
  requirements:
58
- - - "~>"
58
+ - - ">="
59
59
  - !ruby/object:Gem::Version
60
- version: '13.0'
60
+ version: '0'
61
61
  type: :development
62
62
  prerelease: false
63
63
  version_requirements: !ruby/object:Gem::Requirement
64
64
  requirements:
65
- - - "~>"
65
+ - - ">="
66
66
  - !ruby/object:Gem::Version
67
- version: '13.0'
67
+ version: '0'
68
68
  description: A Ruby wrapper around Net::IMAP with an elegant DSL for searching, reading,
69
69
  and managing email.
70
70
  email: code@thoran.com
@@ -85,7 +85,7 @@ files:
85
85
  - test/Imap/Message_test.rb
86
86
  - test/Imap/Search_test.rb
87
87
  - test/Imap/VERSION_test.rb
88
- - test/Imap_test.rb
88
+ - test/imap_test.rb
89
89
  - test/test_helper.rb
90
90
  homepage: https://github.com/thoran/imap
91
91
  licenses:
@@ -109,7 +109,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
109
109
  - !ruby/object:Gem::Version
110
110
  version: '0'
111
111
  requirements: []
112
- rubygems_version: 4.0.20
112
+ rubygems_version: 4.0.22
113
113
  specification_version: 4
114
114
  summary: IMAP for Ruby made easy.
115
115
  test_files: []
File without changes