imapcli 2.0.1 → 3.0.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: ee39530d31868e3c8f3c90a1ad9957552ee91f51e7ed05c57fb9a3724a297ef7
4
- data.tar.gz: 3d6484ebcce956fc9425447786daaf6d7bce486e0b787d5be9e817cba4517dab
3
+ metadata.gz: 5a1983fc995c34ea7b3a8d03cdbc45d644090aa0cc60527fe824cd7ae5227300
4
+ data.tar.gz: f688ac2337ae9c3f0cb5d6d62df60a345bb5fabc0dab771d518be5d1ce1b18a1
5
5
  SHA512:
6
- metadata.gz: 0e6fab7ebd5c9e58b432323e7ee974c8880785ccf969139966ac2bc9bc7f1034db51678e95c35d7fb53a7df434790340c03aedd40bf2d1bfb6308b2d6b6d4614
7
- data.tar.gz: 44801f7d7aa3311683b7e2370b39b9738d4de629e080f32f034b6ae6ac65bda0882dfc15d3731af3929421757a350217f8e4a74ec2127f35d543215cf8aef84e
6
+ metadata.gz: 23e6aaf015e5ffb731e4f5a6e6d926ff0b8988ba3ea92503df7615aff9217e1b1b55a4c5236a9aabade45dffe53f7bf2912e6084886c25ec0f0d83202fc220f9
7
+ data.tar.gz: 5cae5a577598c723f2c9621ad5ad173aa246bf93ffaff740f57e5ab6ce4f3c21b0d124daddf55b801f22b60e677a37a93ae1d20a913da82eff3d644818899833
data/CHANGELOG.md CHANGED
@@ -5,6 +5,21 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [Version 3.0.0 (2025-04-21)][v3.0.0]
9
+
10
+ ### Changed
11
+
12
+ - **BREAKING:** Removed the `-v`/`--verbose` switch which was not functional
13
+ anyway and added a new option `-O`/`--log-output` flag to write IMAP responses
14
+ to a file in JSON format.
15
+
16
+ ## [Version 2.1.0 (2025-03-16)][v2.1.0]
17
+
18
+ ### Added
19
+
20
+ - New command-line flag `-l`/`--limit` to limit the statistics output to
21
+ a given number of mailboxes (folders).
22
+
8
23
  ## [Version 2.0.1 (2025-03-11)][v2.0.1]
9
24
 
10
25
  ### Fixed
@@ -72,6 +87,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
72
87
  - Fix: Docker image.
73
88
  - Fix: Options error message.
74
89
 
90
+ [v3.0.0]: https://github.com/bovender/imapcli/releases/tag/v3.0.0
91
+ [v2.1.0]: https://github.com/bovender/imapcli/releases/tag/v2.1.0
75
92
  [v2.0.1]: https://github.com/bovender/imapcli/releases/tag/v2.0.1
76
93
  [v2.0.0]: https://github.com/bovender/imapcli/releases/tag/v2.0.0
77
94
  [v1.0.7]: https://github.com/bovender/imapcli/releases/tag/v1.0.7
data/Gemfile.lock CHANGED
@@ -1,13 +1,15 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- imapcli (2.0.0)
4
+ imapcli (2.1.0)
5
5
  activesupport (~> 8.0)
6
6
  csv
7
7
  descriptive_statistics (~> 2.5)
8
8
  dotenv (~> 3.1)
9
9
  gli (~> 2.22)
10
+ multi_json (~> 1.15)
10
11
  net-imap
12
+ oj (~> 3.16)
11
13
  tty-progressbar (~> 0.18)
12
14
  tty-prompt (~> 0.23)
13
15
  tty-table (~> 0.12)
@@ -16,7 +18,7 @@ PATH
16
18
  GEM
17
19
  remote: https://rubygems.org/
18
20
  specs:
19
- activesupport (8.0.1)
21
+ activesupport (8.0.2)
20
22
  base64
21
23
  benchmark (>= 0.3)
22
24
  bigdecimal
@@ -83,7 +85,7 @@ GEM
83
85
  pp (>= 0.6.0)
84
86
  rdoc (>= 4.0.0)
85
87
  reline (>= 0.4.2)
86
- json (2.10.1)
88
+ json (2.10.2)
87
89
  language_server-protocol (3.17.0.4)
88
90
  lint_roller (1.1.0)
89
91
  listen (3.9.0)
@@ -92,7 +94,8 @@ GEM
92
94
  logger (1.6.6)
93
95
  lumberjack (1.2.10)
94
96
  method_source (1.1.0)
95
- minitest (5.25.4)
97
+ minitest (5.25.5)
98
+ multi_json (1.15.0)
96
99
  nenv (0.3.0)
97
100
  net-imap (0.5.6)
98
101
  date
@@ -102,6 +105,9 @@ GEM
102
105
  notiffany (0.1.3)
103
106
  nenv (~> 0.1)
104
107
  shellany (~> 0.0)
108
+ oj (3.16.10)
109
+ bigdecimal (>= 3.0)
110
+ ostruct (>= 0.2)
105
111
  ostruct (0.6.1)
106
112
  parallel (1.26.3)
107
113
  parser (3.3.7.1)
@@ -142,7 +148,7 @@ GEM
142
148
  diff-lcs (>= 1.2.0, < 2.0)
143
149
  rspec-support (~> 3.13.0)
144
150
  rspec-support (3.13.2)
145
- rubocop (1.73.2)
151
+ rubocop (1.74.0)
146
152
  json (~> 2.3)
147
153
  language_server-protocol (~> 3.17.0.2)
148
154
  lint_roller (~> 1.1.0)
@@ -153,7 +159,7 @@ GEM
153
159
  rubocop-ast (>= 1.38.0, < 2.0)
154
160
  ruby-progressbar (~> 1.7)
155
161
  unicode-display_width (>= 2.4.0, < 4.0)
156
- rubocop-ast (1.38.1)
162
+ rubocop-ast (1.39.0)
157
163
  parser (>= 3.3.1.0)
158
164
  rubocop-performance (1.24.0)
159
165
  lint_roller (~> 1.1)
data/README.md CHANGED
@@ -263,12 +263,34 @@ keyword, as shown below.
263
263
 
264
264
  Example:
265
265
 
266
- bundle exec -it bin/imapcli -s yourserver.example.com -u myusername -P stats -r -o max_size Archive
266
+ docker run -it bin/imapcli -s yourserver.example.com -u myusername -P stats -r -o max_size Archive
267
+
268
+ #### Limiting the number of mailboxes
269
+
270
+ You can limit the number of mailboxes (folders) in the output table with the
271
+ `-l`/`--limit` flag. This flag takes an argument that indicates the maximum
272
+ number of mailboxes. THe default value for this argument is 10.
273
+
274
+ Use case: Say you have a very large IMAP account, and you would like to know the
275
+ 5 folders that contain the largest messages. The command should be:
276
+
277
+ imapcli -s yourserver.example.com -u myusername -P stats -o max_size --limit 5
267
278
 
268
279
  #### Obtaining comma-separated values (CSV)
269
280
 
270
281
  Use the `--csv` flag.
271
282
 
283
+ ### Writing IMAP responses to file
284
+
285
+ Using the `-O`/`--log-output` flag, you can have `imapcli` write the IMAP responses
286
+ to a file in JSON format:
287
+
288
+ ```bash
289
+ imapcli -s yourserver.example.com -u username -P -O output_file.json stats Inbox
290
+ ```
291
+
292
+ **WARNING:** The output file will be overwritten without notice!
293
+
272
294
  ## Alternative resources
273
295
 
274
296
  While researching command-line tools for IMAP servers, I came across the
data/imapcli.gemspec CHANGED
@@ -28,6 +28,7 @@ Gem::Specification.new do |s|
28
28
  s.add_dependency('dotenv', '~> 3.1')
29
29
  s.add_dependency('gli', '~> 2.22')
30
30
  s.add_dependency('net-imap')
31
+ s.add_dependency('oj', '~> 3.16')
31
32
  s.add_dependency('tty-progressbar', '~> 0.18')
32
33
  s.add_dependency('tty-prompt', '~> 0.23')
33
34
  s.add_dependency('tty-table', '~> 0.12')
data/lib/imapcli/cli.rb CHANGED
@@ -1,5 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'multi_json'
4
+ require 'oj'
5
+
3
6
  module Imapcli
4
7
  class Cli # rubocop:disable Metrics/ClassLength,Style/Documentation
5
8
  extend GLI::App
@@ -31,8 +34,8 @@ module Imapcli
31
34
  desc 'Prompt for password'
32
35
  switch %i[P prompt], negatable: false
33
36
 
34
- desc 'Verbose output (e.g., response values from Rubys Net::IMAP)'
35
- switch %i[v verbose], negatable: false
37
+ desc 'Output responses from Rubys Net::IMAP to file in JSON format. File will be overwritten if it exists!'
38
+ flag %i[O log-output]
36
39
 
37
40
  desc 'Tests if the server is available and log-in succeeds with the credentials'
38
41
  command :check do |c|
@@ -74,6 +77,10 @@ module Imapcli
74
77
  default: 'total_size'
75
78
  c.switch %i[reverse], desc: 'Reverse sort order (largest first)', negatable: false
76
79
  c.switch [:csv], desc: 'Output comma-separated values (CSV)'
80
+ c.flag %i[l limit],
81
+ desc: 'Limit the results to n folders (IMAP mailboxes)',
82
+ arg_name: 'max_lines',
83
+ default: 10
77
84
 
78
85
  c.action do |_global_options, options, args| # rubocop:disable Metrics/BlockLength
79
86
  raise unless @validator.stats_options_valid?(options, args)
@@ -85,7 +92,7 @@ module Imapcli
85
92
  if progress_bar
86
93
  progress_bar.advance
87
94
  else
88
- @prompt.say "info: collecting stats for #{n} folders" if n > 1
95
+ @prompt.warn "info: collecting stats for #{n} folders" if n > 1
89
96
  progress_bar = TTY::ProgressBar.new(
90
97
  'collecting stats... :current/:total (:percent, :eta remaining)',
91
98
  total: n, clear: true
@@ -107,9 +114,9 @@ module Imapcli
107
114
  formatted_body = formatted_body.insert(0, :separator).insert(-2, :separator)
108
115
 
109
116
  if options[:human]
110
- @prompt.say "notice: -H/--human flag present, message sizes are given with SI prefixes"
117
+ @prompt.warn "notice: -H/--human flag present, message sizes are given with SI prefixes"
111
118
  else
112
- @prompt.say "notice: message sizes are given in bytes"
119
+ @prompt.warn "notice: message sizes are given in bytes"
113
120
  end
114
121
 
115
122
  table = TTY::Table.new(head, formatted_body)
@@ -139,27 +146,25 @@ module Imapcli
139
146
  global[:p] = @prompt.mask 'Enter password:' if global[:P]
140
147
  global[:p] ||= ENV.fetch('IMAP_PASS', nil)
141
148
 
142
- client = Imapcli::Client.new(global[:s], global[:u], global[:p])
149
+ @client = Imapcli::Client.new(global[:s], global[:u], global[:p])
143
150
  @prompt.say "server: #{global[:s]}"
144
151
  @prompt.say "user: #{global[:u]}"
145
- raise 'invalid server name' unless client.server_valid?
146
- raise 'invalid user name' unless client.user_valid?
152
+ raise 'invalid server name' unless @client.server_valid?
153
+ raise 'invalid user name' unless @client.user_valid?
147
154
 
148
155
  @prompt.warn 'warning: no password was provided (missing -p/-P option)' unless global[:p]
149
- raise 'unable to connect to server' unless client.connection
156
+ raise 'unable to connect to server' unless @client.connection
150
157
 
151
- @command = Imapcli::Command.new(client)
158
+ @command = Imapcli::Command.new(@client)
152
159
 
153
160
  true
154
161
  end
155
162
 
156
163
  post do |global, _command, _options, _args|
157
164
  @client&.logout
158
- if global[:v]
159
- @prompt.say "\n>>> --verbose switch on, listing server responses <<<"
160
- @client.responses.each do |response|
161
- @prompt.say response
162
- end
165
+ if file_name = global[:o]
166
+ @prompt.warn "notice: writing IMAP responses to #{file_name.dump}"
167
+ File.write(file_name, Oj.dump(@client.responses, mode: :object, indent: 2, escape: :unicode_xss, symbolize_names: false))
163
168
  end
164
169
  end
165
170
 
@@ -1,10 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'json'
4
+
3
5
  module Imapcli
4
6
  # Wrapper for Net::IMAP
5
7
  class Client # rubocop:disable Metrics/ClassLength
6
8
  attr_accessor :port, :user, :pass
7
- attr_reader :responses
8
9
 
9
10
  ## Initializs the Client class.
10
11
  ##
@@ -17,7 +18,7 @@ module Imapcli
17
18
  self.server = server_with_optional_port
18
19
  @user = user
19
20
  @pass = pass
20
- clear_log
21
+ clear_responses
21
22
  end
22
23
 
23
24
  # Attribute reader for the server domain name
@@ -57,10 +58,15 @@ module Imapcli
57
58
  end
58
59
 
59
60
  # Clears the server response log
60
- def clear_log
61
+ def clear_responses
61
62
  @log = []
62
63
  end
63
64
 
65
+ # Returns the IMAP server response log
66
+ def responses
67
+ @log
68
+ end
69
+
64
70
  # Returns the last response from the server
65
71
  def last_response
66
72
  @log.last
@@ -96,17 +102,17 @@ module Imapcli
96
102
  # Returns the server's greeting (which may reveal the server software name
97
103
  # such as 'Dovecot').
98
104
  def greeting
99
- query_server { connection.greeting.data.text.strip }
105
+ query_server('greeting') { connection.greeting.data.text.strip }
100
106
  end
101
107
 
102
108
  # Returns the server's capabilities.
103
109
  def capability
104
- @capability ||= query_server { connection.capability }
110
+ @capability ||= query_server('capability') { connection.capability }
105
111
  end
106
112
 
107
113
  # Returns the character that is used to separate nested mailbox names.
108
114
  def separator
109
- @separator ||= query_server { connection.list('', '')[0].delim }
115
+ @separator ||= query_server("list('')") { connection.list('', '')[0].delim }
110
116
  end
111
117
 
112
118
  # Returns true if the server supports the IMAP QUOTA extension.
@@ -120,7 +126,7 @@ module Imapcli
120
126
  return unless supports_quota
121
127
 
122
128
  @quota ||= begin
123
- info = query_server { @connection.getquotaroot('INBOX')[1] }
129
+ info = query_server("getquotaroot('INBOX')") { @connection.getquotaroot('INBOX')[1] }
124
130
  percent = info.quota.to_i.positive? ? info.usage.to_i.fdiv(info.quota.to_i) * 100 : nil
125
131
  [info.usage, info.quota, percent]
126
132
  end
@@ -130,8 +136,8 @@ module Imapcli
130
136
  #
131
137
  # The value is currently NOT cached.
132
138
  def messages(mailbox)
133
- query_server { connection.examine(mailbox) }
134
- query_server { connection.search('ALL') }
139
+ query_server("examine('#{mailbox}')") { connection.examine(mailbox) }
140
+ query_server("search('ALL')") { connection.search('ALL') }
135
141
  end
136
142
 
137
143
  # Examines a mailbox and returns statistics about the messages in it.
@@ -149,7 +155,7 @@ module Imapcli
149
155
  if messages.empty?
150
156
  []
151
157
  else
152
- query_server do
158
+ query_server('fetch(...)') do
153
159
  messages.each_slice(1000).map do |some_messages|
154
160
  connection.fetch(some_messages, 'RFC822.SIZE').map do |f|
155
161
  f.attr['RFC822.SIZE']
@@ -168,7 +174,7 @@ module Imapcli
168
174
  #
169
175
  # The value is cached.
170
176
  def mailboxes
171
- @mailboxes ||= query_server { @connection.list('', '*') }
177
+ @mailboxes ||= query_server('list') { @connection.list('', '*') }
172
178
  end
173
179
 
174
180
  # Returns a tree of +Imapcli::Mailbox+ objects.
@@ -188,12 +194,10 @@ module Imapcli
188
194
  private
189
195
 
190
196
  def response_ok?(response)
191
- @log << response
192
197
  response.name == 'OK'
193
198
  end
194
199
 
195
200
  def log_error(error)
196
- @log << error
197
201
  false
198
202
  end
199
203
 
@@ -203,13 +207,31 @@ module Imapcli
203
207
  # error if not. The code that queries the server must be contained in the
204
208
  # +block+, and the +block+'s return value is returned by this function.
205
209
  # The +connection+'s responses are logged.
206
- def query_server
210
+ def query_server(imap_command)
207
211
  raise('no connection to a server') unless connection
208
212
 
209
213
  result = yield
210
- @log << connection.responses
214
+ @log << {
215
+ 'imap_command': imap_command,
216
+ 'imap_response': connection.responses.to_h
217
+ }
211
218
  result
212
219
  end
213
220
 
221
+ # Recursively convert structs in an array of hashes to hashes
222
+ # Inspired by https://stackoverflow.com/a/62804063/270712
223
+ # and rephrased to improve readability a bit
224
+ def to_h_recursively(hash)
225
+ hash.map do |value|
226
+ if value.is_a?(Arry)
227
+ to_h_recursively(value)
228
+ elsif value.is_a?(Struct)
229
+ value.to_h
230
+ else
231
+ value
232
+ end
233
+ end
234
+ end
235
+
214
236
  end
215
237
  end
@@ -68,7 +68,12 @@ module Imapcli
68
68
  yield current_count if block_given?
69
69
  end
70
70
  end
71
- output = sorted_list(list, options).map do |mailbox|
71
+
72
+ output = if options[:limit]
73
+ sorted_list(list, options).last(options[:limit].to_i)
74
+ else
75
+ sorted_list(list, options)
76
+ end.map do |mailbox|
72
77
  stats_to_table(mailbox.full_name, mailbox.stats)
73
78
  end
74
79
  output << stats_to_table('Total', total_stats) if list.length > 1
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Imapcli
4
- VERSION = '2.0.1'
4
+ VERSION = '3.0.0'
5
5
  end
data/logoutput.json ADDED
@@ -0,0 +1,527 @@
1
+ [
2
+ {
3
+ ":imap_command":"list",
4
+ ":imap_response":{
5
+ "FLAGS":[
6
+ [
7
+ ":Answered",
8
+ ":Flagged",
9
+ ":Deleted",
10
+ ":Seen",
11
+ ":Draft",
12
+ "NonJunk",
13
+ "Junk",
14
+ "$label2",
15
+ "$forwarded",
16
+ "$label5",
17
+ "Old",
18
+ "$label4",
19
+ "$label3",
20
+ "$SIGNED",
21
+ "$JUNK",
22
+ "$ATTACHMENT",
23
+ "$NOTJUNK",
24
+ "$INVITATION",
25
+ "$label1",
26
+ "$TODO"
27
+ ]
28
+ ],
29
+ "OK":[
30
+ {
31
+ "^u":[
32
+ "Net::IMAP::ResponseText",
33
+ {
34
+ "^u":[
35
+ "Net::IMAP::ResponseCode",
36
+ "PERMANENTFLAGS",
37
+ []]},
38
+ "Read-only mailbox."]},
39
+ {
40
+ "^u":[
41
+ "Net::IMAP::ResponseText",
42
+ {
43
+ "^u":[
44
+ "Net::IMAP::ResponseCode",
45
+ "UNSEEN",
46
+ 6]},
47
+ "First unseen."]},
48
+ {
49
+ "^u":[
50
+ "Net::IMAP::ResponseText",
51
+ {
52
+ "^u":[
53
+ "Net::IMAP::ResponseCode",
54
+ "UIDVALIDITY",
55
+ 1435851445]},
56
+ "UIDs valid"]},
57
+ {
58
+ "^u":[
59
+ "Net::IMAP::ResponseText",
60
+ {
61
+ "^u":[
62
+ "Net::IMAP::ResponseCode",
63
+ "UIDNEXT",
64
+ 104489]},
65
+ "Predicted next UID"]},
66
+ {
67
+ "^u":[
68
+ "Net::IMAP::ResponseText",
69
+ {
70
+ "^u":[
71
+ "Net::IMAP::ResponseCode",
72
+ "HIGHESTMODSEQ",
73
+ 309489]},
74
+ "Highest"]}
75
+ ],
76
+ "PERMANENTFLAGS":[
77
+ []
78
+ ],
79
+ "EXISTS":[
80
+ 6
81
+ ],
82
+ "RECENT":[
83
+ 0
84
+ ],
85
+ "UNSEEN":[
86
+ 6
87
+ ],
88
+ "UIDVALIDITY":[
89
+ 1435851445
90
+ ],
91
+ "UIDNEXT":[
92
+ 104489
93
+ ],
94
+ "HIGHESTMODSEQ":[
95
+ 309489
96
+ ],
97
+ "ESEARCH":[],
98
+ "BYE":[
99
+ {
100
+ "^u":[
101
+ "Net::IMAP::ResponseText",
102
+ null,
103
+ "Logging out"]}
104
+ ]
105
+ }
106
+ },
107
+ {
108
+ ":imap_command":"list('')",
109
+ ":imap_response":{
110
+ "FLAGS":[
111
+ [
112
+ ":Answered",
113
+ ":Flagged",
114
+ ":Deleted",
115
+ ":Seen",
116
+ ":Draft",
117
+ "NonJunk",
118
+ "Junk",
119
+ "$label2",
120
+ "$forwarded",
121
+ "$label5",
122
+ "Old",
123
+ "$label4",
124
+ "$label3",
125
+ "$SIGNED",
126
+ "$JUNK",
127
+ "$ATTACHMENT",
128
+ "$NOTJUNK",
129
+ "$INVITATION",
130
+ "$label1",
131
+ "$TODO"
132
+ ]
133
+ ],
134
+ "OK":[
135
+ {
136
+ "^u":[
137
+ "Net::IMAP::ResponseText",
138
+ {
139
+ "^u":[
140
+ "Net::IMAP::ResponseCode",
141
+ "PERMANENTFLAGS",
142
+ []]},
143
+ "Read-only mailbox."]},
144
+ {
145
+ "^u":[
146
+ "Net::IMAP::ResponseText",
147
+ {
148
+ "^u":[
149
+ "Net::IMAP::ResponseCode",
150
+ "UNSEEN",
151
+ 6]},
152
+ "First unseen."]},
153
+ {
154
+ "^u":[
155
+ "Net::IMAP::ResponseText",
156
+ {
157
+ "^u":[
158
+ "Net::IMAP::ResponseCode",
159
+ "UIDVALIDITY",
160
+ 1435851445]},
161
+ "UIDs valid"]},
162
+ {
163
+ "^u":[
164
+ "Net::IMAP::ResponseText",
165
+ {
166
+ "^u":[
167
+ "Net::IMAP::ResponseCode",
168
+ "UIDNEXT",
169
+ 104489]},
170
+ "Predicted next UID"]},
171
+ {
172
+ "^u":[
173
+ "Net::IMAP::ResponseText",
174
+ {
175
+ "^u":[
176
+ "Net::IMAP::ResponseCode",
177
+ "HIGHESTMODSEQ",
178
+ 309489]},
179
+ "Highest"]}
180
+ ],
181
+ "PERMANENTFLAGS":[
182
+ []
183
+ ],
184
+ "EXISTS":[
185
+ 6
186
+ ],
187
+ "RECENT":[
188
+ 0
189
+ ],
190
+ "UNSEEN":[
191
+ 6
192
+ ],
193
+ "UIDVALIDITY":[
194
+ 1435851445
195
+ ],
196
+ "UIDNEXT":[
197
+ 104489
198
+ ],
199
+ "HIGHESTMODSEQ":[
200
+ 309489
201
+ ],
202
+ "ESEARCH":[],
203
+ "BYE":[
204
+ {
205
+ "^u":[
206
+ "Net::IMAP::ResponseText",
207
+ null,
208
+ "Logging out"]}
209
+ ]
210
+ }
211
+ },
212
+ {
213
+ ":imap_command":"examine('INBOX')",
214
+ ":imap_response":{
215
+ "FLAGS":[
216
+ [
217
+ ":Answered",
218
+ ":Flagged",
219
+ ":Deleted",
220
+ ":Seen",
221
+ ":Draft",
222
+ "NonJunk",
223
+ "Junk",
224
+ "$label2",
225
+ "$forwarded",
226
+ "$label5",
227
+ "Old",
228
+ "$label4",
229
+ "$label3",
230
+ "$SIGNED",
231
+ "$JUNK",
232
+ "$ATTACHMENT",
233
+ "$NOTJUNK",
234
+ "$INVITATION",
235
+ "$label1",
236
+ "$TODO"
237
+ ]
238
+ ],
239
+ "OK":[
240
+ {
241
+ "^u":[
242
+ "Net::IMAP::ResponseText",
243
+ {
244
+ "^u":[
245
+ "Net::IMAP::ResponseCode",
246
+ "PERMANENTFLAGS",
247
+ []]},
248
+ "Read-only mailbox."]},
249
+ {
250
+ "^u":[
251
+ "Net::IMAP::ResponseText",
252
+ {
253
+ "^u":[
254
+ "Net::IMAP::ResponseCode",
255
+ "UNSEEN",
256
+ 6]},
257
+ "First unseen."]},
258
+ {
259
+ "^u":[
260
+ "Net::IMAP::ResponseText",
261
+ {
262
+ "^u":[
263
+ "Net::IMAP::ResponseCode",
264
+ "UIDVALIDITY",
265
+ 1435851445]},
266
+ "UIDs valid"]},
267
+ {
268
+ "^u":[
269
+ "Net::IMAP::ResponseText",
270
+ {
271
+ "^u":[
272
+ "Net::IMAP::ResponseCode",
273
+ "UIDNEXT",
274
+ 104489]},
275
+ "Predicted next UID"]},
276
+ {
277
+ "^u":[
278
+ "Net::IMAP::ResponseText",
279
+ {
280
+ "^u":[
281
+ "Net::IMAP::ResponseCode",
282
+ "HIGHESTMODSEQ",
283
+ 309489]},
284
+ "Highest"]}
285
+ ],
286
+ "PERMANENTFLAGS":[
287
+ []
288
+ ],
289
+ "EXISTS":[
290
+ 6
291
+ ],
292
+ "RECENT":[
293
+ 0
294
+ ],
295
+ "UNSEEN":[
296
+ 6
297
+ ],
298
+ "UIDVALIDITY":[
299
+ 1435851445
300
+ ],
301
+ "UIDNEXT":[
302
+ 104489
303
+ ],
304
+ "HIGHESTMODSEQ":[
305
+ 309489
306
+ ],
307
+ "ESEARCH":[],
308
+ "BYE":[
309
+ {
310
+ "^u":[
311
+ "Net::IMAP::ResponseText",
312
+ null,
313
+ "Logging out"]}
314
+ ]
315
+ }
316
+ },
317
+ {
318
+ ":imap_command":"search('ALL')",
319
+ ":imap_response":{
320
+ "FLAGS":[
321
+ [
322
+ ":Answered",
323
+ ":Flagged",
324
+ ":Deleted",
325
+ ":Seen",
326
+ ":Draft",
327
+ "NonJunk",
328
+ "Junk",
329
+ "$label2",
330
+ "$forwarded",
331
+ "$label5",
332
+ "Old",
333
+ "$label4",
334
+ "$label3",
335
+ "$SIGNED",
336
+ "$JUNK",
337
+ "$ATTACHMENT",
338
+ "$NOTJUNK",
339
+ "$INVITATION",
340
+ "$label1",
341
+ "$TODO"
342
+ ]
343
+ ],
344
+ "OK":[
345
+ {
346
+ "^u":[
347
+ "Net::IMAP::ResponseText",
348
+ {
349
+ "^u":[
350
+ "Net::IMAP::ResponseCode",
351
+ "PERMANENTFLAGS",
352
+ []]},
353
+ "Read-only mailbox."]},
354
+ {
355
+ "^u":[
356
+ "Net::IMAP::ResponseText",
357
+ {
358
+ "^u":[
359
+ "Net::IMAP::ResponseCode",
360
+ "UNSEEN",
361
+ 6]},
362
+ "First unseen."]},
363
+ {
364
+ "^u":[
365
+ "Net::IMAP::ResponseText",
366
+ {
367
+ "^u":[
368
+ "Net::IMAP::ResponseCode",
369
+ "UIDVALIDITY",
370
+ 1435851445]},
371
+ "UIDs valid"]},
372
+ {
373
+ "^u":[
374
+ "Net::IMAP::ResponseText",
375
+ {
376
+ "^u":[
377
+ "Net::IMAP::ResponseCode",
378
+ "UIDNEXT",
379
+ 104489]},
380
+ "Predicted next UID"]},
381
+ {
382
+ "^u":[
383
+ "Net::IMAP::ResponseText",
384
+ {
385
+ "^u":[
386
+ "Net::IMAP::ResponseCode",
387
+ "HIGHESTMODSEQ",
388
+ 309489]},
389
+ "Highest"]}
390
+ ],
391
+ "PERMANENTFLAGS":[
392
+ []
393
+ ],
394
+ "EXISTS":[
395
+ 6
396
+ ],
397
+ "RECENT":[
398
+ 0
399
+ ],
400
+ "UNSEEN":[
401
+ 6
402
+ ],
403
+ "UIDVALIDITY":[
404
+ 1435851445
405
+ ],
406
+ "UIDNEXT":[
407
+ 104489
408
+ ],
409
+ "HIGHESTMODSEQ":[
410
+ 309489
411
+ ],
412
+ "ESEARCH":[],
413
+ "BYE":[
414
+ {
415
+ "^u":[
416
+ "Net::IMAP::ResponseText",
417
+ null,
418
+ "Logging out"]}
419
+ ]
420
+ }
421
+ },
422
+ {
423
+ ":imap_command":"fetch(...)",
424
+ ":imap_response":{
425
+ "FLAGS":[
426
+ [
427
+ ":Answered",
428
+ ":Flagged",
429
+ ":Deleted",
430
+ ":Seen",
431
+ ":Draft",
432
+ "NonJunk",
433
+ "Junk",
434
+ "$label2",
435
+ "$forwarded",
436
+ "$label5",
437
+ "Old",
438
+ "$label4",
439
+ "$label3",
440
+ "$SIGNED",
441
+ "$JUNK",
442
+ "$ATTACHMENT",
443
+ "$NOTJUNK",
444
+ "$INVITATION",
445
+ "$label1",
446
+ "$TODO"
447
+ ]
448
+ ],
449
+ "OK":[
450
+ {
451
+ "^u":[
452
+ "Net::IMAP::ResponseText",
453
+ {
454
+ "^u":[
455
+ "Net::IMAP::ResponseCode",
456
+ "PERMANENTFLAGS",
457
+ []]},
458
+ "Read-only mailbox."]},
459
+ {
460
+ "^u":[
461
+ "Net::IMAP::ResponseText",
462
+ {
463
+ "^u":[
464
+ "Net::IMAP::ResponseCode",
465
+ "UNSEEN",
466
+ 6]},
467
+ "First unseen."]},
468
+ {
469
+ "^u":[
470
+ "Net::IMAP::ResponseText",
471
+ {
472
+ "^u":[
473
+ "Net::IMAP::ResponseCode",
474
+ "UIDVALIDITY",
475
+ 1435851445]},
476
+ "UIDs valid"]},
477
+ {
478
+ "^u":[
479
+ "Net::IMAP::ResponseText",
480
+ {
481
+ "^u":[
482
+ "Net::IMAP::ResponseCode",
483
+ "UIDNEXT",
484
+ 104489]},
485
+ "Predicted next UID"]},
486
+ {
487
+ "^u":[
488
+ "Net::IMAP::ResponseText",
489
+ {
490
+ "^u":[
491
+ "Net::IMAP::ResponseCode",
492
+ "HIGHESTMODSEQ",
493
+ 309489]},
494
+ "Highest"]}
495
+ ],
496
+ "PERMANENTFLAGS":[
497
+ []
498
+ ],
499
+ "EXISTS":[
500
+ 6
501
+ ],
502
+ "RECENT":[
503
+ 0
504
+ ],
505
+ "UNSEEN":[
506
+ 6
507
+ ],
508
+ "UIDVALIDITY":[
509
+ 1435851445
510
+ ],
511
+ "UIDNEXT":[
512
+ 104489
513
+ ],
514
+ "HIGHESTMODSEQ":[
515
+ 309489
516
+ ],
517
+ "ESEARCH":[],
518
+ "BYE":[
519
+ {
520
+ "^u":[
521
+ "Net::IMAP::ResponseText",
522
+ null,
523
+ "Logging out"]}
524
+ ]
525
+ }
526
+ }
527
+ ]
@@ -133,6 +133,11 @@ RSpec.describe Imapcli::Command do
133
133
  expect(output).to be_a Array
134
134
  expect(output[0][0]).to eq 'Inbox/Smallest/Messages'
135
135
  end
136
+
137
+ it 'returns only the first n mailboxes' do
138
+ output = command.stats('Inbox', depth: -1, limit: 2)
139
+ expect(output.length).to eq 3 # including summary line
140
+ end
136
141
  end
137
142
 
138
143
  end
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: imapcli
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.1
4
+ version: 3.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Kraus (bovender)
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2025-03-11 00:00:00.000000000 Z
10
+ date: 2025-04-21 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: activesupport
@@ -94,6 +93,20 @@ dependencies:
94
93
  - - ">="
95
94
  - !ruby/object:Gem::Version
96
95
  version: '0'
96
+ - !ruby/object:Gem::Dependency
97
+ name: oj
98
+ requirement: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - "~>"
101
+ - !ruby/object:Gem::Version
102
+ version: '3.16'
103
+ type: :runtime
104
+ prerelease: false
105
+ version_requirements: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - "~>"
108
+ - !ruby/object:Gem::Version
109
+ version: '3.16'
97
110
  - !ruby/object:Gem::Dependency
98
111
  name: tty-progressbar
99
112
  requirement: !ruby/object:Gem::Requirement
@@ -150,7 +163,6 @@ dependencies:
150
163
  - - "~>"
151
164
  - !ruby/object:Gem::Version
152
165
  version: 2.7.0
153
- description:
154
166
  email: bovender@bovender.de
155
167
  executables:
156
168
  - imapcli
@@ -186,6 +198,7 @@ files:
186
198
  - lib/imapcli/option_validator.rb
187
199
  - lib/imapcli/stats.rb
188
200
  - lib/imapcli/version.rb
201
+ - logoutput.json
189
202
  - spec/lib/imapcli/client_spec.rb
190
203
  - spec/lib/imapcli/command_spec.rb
191
204
  - spec/lib/imapcli/mailbox_spec.rb
@@ -195,7 +208,6 @@ homepage: https://github.com/bovender/imapcli
195
208
  licenses:
196
209
  - Apache-2.0
197
210
  metadata: {}
198
- post_install_message:
199
211
  rdoc_options:
200
212
  - "--title"
201
213
  - imapcli
@@ -215,8 +227,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
215
227
  - !ruby/object:Gem::Version
216
228
  version: '0'
217
229
  requirements: []
218
- rubygems_version: 3.5.22
219
- signing_key:
230
+ rubygems_version: 3.6.2
220
231
  specification_version: 4
221
232
  summary: Command-line tool to query IMAP servers
222
233
  test_files: []