diff --git a/lib/net/imap.rb b/lib/net/imap.rb index 4c7607c2..e1ce6300 100644 --- a/lib/net/imap.rb +++ b/lib/net/imap.rb @@ -288,6 +288,8 @@ module Net # pre-authenticated connection. # - #responses: Yields unhandled UntaggedResponse#data and non-+nil+ # ResponseCode#data. + # - #extract_responses: Removes and returns the responses for which the block + # returns a true value. # - #clear_responses: Deletes unhandled data from #responses and returns it. # - #add_response_handler: Add a block to be called inside the receiver thread # with every server response. @@ -2534,7 +2536,7 @@ def idle_done # return the TaggedResponse directly, #add_response_handler must be used to # handle all response codes. # - # Related: #clear_responses, #response_handlers, #greeting + # Related: #extract_responses, #clear_responses, #response_handlers, #greeting def responses(type = nil) if block_given? synchronize { yield(type ? @responses[type.to_s.upcase] : @responses) } @@ -2562,7 +2564,7 @@ def responses(type = nil) # Clearing responses is synchronized with other threads. The lock is # released before returning. # - # Related: #responses, #response_handlers + # Related: #extract_responses, #responses, #response_handlers def clear_responses(type = nil) synchronize { if type @@ -2576,6 +2578,30 @@ def clear_responses(type = nil) .freeze end + # :call-seq: + # extract_responses(type) {|response| ... } -> array + # + # Yields all of the unhandled #responses for a single response +type+. + # Removes and returns the responses for which the block returns a true + # value. + # + # Extracting responses is synchronized with other threads. The lock is + # released before returning. + # + # Related: #responses, #clear_responses + def extract_responses(type) + type = String.try_convert(type) or + raise ArgumentError, "type must be a string" + raise ArgumentError, "must provide a block" unless block_given? + extracted = [] + responses(type) do |all| + all.reject! do |response| + extracted << response if yield response + end + end + extracted + end + # Returns all response handlers, including those that are added internally # by commands. Each response handler will be called with every new # UntaggedResponse, TaggedResponse, and ContinuationRequest. diff --git a/test/net/imap/test_imap.rb b/test/net/imap/test_imap.rb index b76ee84d..97e9a14d 100644 --- a/test/net/imap/test_imap.rb +++ b/test/net/imap/test_imap.rb @@ -1110,7 +1110,7 @@ def test_enable end end - def test_responses + test "#responses" do with_fake_server do |server, imap| # responses available before SELECT/EXAMINE assert_equal(%w[IMAP4REV1 NAMESPACE MOVE IDLE UTF8=ACCEPT], @@ -1144,7 +1144,7 @@ def test_responses end end - def test_clear_responses + test "#clear_responses" do with_fake_server do |server, imap| resp = imap.select "INBOX" assert_equal([Net::IMAP::TaggedResponse, "RUBY0001", "OK"], @@ -1168,6 +1168,49 @@ def test_clear_responses end end + test "#extract_responses" do + with_fake_server do |server, imap| + resp = imap.select "INBOX" + assert_equal([Net::IMAP::TaggedResponse, "RUBY0001", "OK"], + [resp.class, resp.tag, resp.name]) + # Need to send a string type and a block + assert_raise(ArgumentError) do imap.extract_responses { true } end + assert_raise(ArgumentError) do imap.extract_responses(nil) { true } end + assert_raise(ArgumentError) do imap.extract_responses("OK") end + # matching nothing + assert_equal([172], imap.responses("EXISTS", &:dup)) + assert_equal([], imap.extract_responses("EXISTS") { String === _1 }) + assert_equal([172], imap.responses("EXISTS", &:dup)) + # matching everything + assert_equal([172], imap.responses("EXISTS", &:dup)) + assert_equal([172], imap.extract_responses("EXISTS", &:even?)) + assert_equal([], imap.responses("EXISTS", &:dup)) + # matching some + server.unsolicited("101 FETCH (UID 1111 FLAGS (\\Seen))") + server.unsolicited("102 FETCH (UID 2222 FLAGS (\\Seen \\Flagged))") + server.unsolicited("103 FETCH (UID 3333 FLAGS (\\Deleted))") + wait_for_response_count(imap, type: "FETCH", count: 3) + + result = imap.extract_responses("FETCH") { _1.flags.include?(:Flagged) } + assert_equal( + [ + Net::IMAP::FetchData.new( + 102, {"UID" => 2222, "FLAGS" => [:Seen, :Flagged]} + ), + ], + result, + ) + assert_equal 2, imap.responses("FETCH", &:count) + + result = imap.extract_responses("FETCH") { _1.flags.include?(:Deleted) } + assert_equal( + [Net::IMAP::FetchData.new(103, {"UID" => 3333, "FLAGS" => [:Deleted]})], + result + ) + assert_equal 1, imap.responses("FETCH", &:count) + end + end + test "#select with condstore" do with_fake_server do |server, imap| imap.select "inbox", condstore: true