imapcli 2.0.0 → 2.1.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.md +15 -0
- data/Dockerfile +4 -3
- data/Gemfile.lock +7 -7
- data/README.md +17 -14
- data/imapcli.gemspec +1 -1
- data/lib/imapcli/cli.rb +4 -0
- data/lib/imapcli/command.rb +30 -23
- data/lib/imapcli/version.rb +1 -1
- data/spec/lib/imapcli/command_spec.rb +40 -23
- metadata +16 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0736f0dbe2cb473770fb5f92f0f37546b9f26a6e892bbca3c69b072c1f06db9f
|
4
|
+
data.tar.gz: '0609a383ac74799eb2aade3d52fa0c5c811fc5cb4b253f1607e0b6f2dbe7cf01'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 69d818a1780fda1549bbd83581c2d0abbc4ecda7274cb411bedd450a8365e322153263c1f5b490b03a183e7c8cb02e5d382c82c8628ee43e1c1c1294f6e76afa
|
7
|
+
data.tar.gz: e664577a0949609bebd90a7bc08b94e4559001fcb7b26429958fe38801acd835089b030cad3d3b1fa09a95ccc51b400f6cb8f66befc4903ebe1cb9b4bb56746d
|
data/CHANGELOG.md
CHANGED
@@ -5,6 +5,19 @@ 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 2.1.0 (2025-03-16)][v2.1.0]
|
9
|
+
|
10
|
+
### Added
|
11
|
+
|
12
|
+
- New command-line flag `-l`/`--limit` to limit the statistics output to
|
13
|
+
a given number of mailboxes (folders).
|
14
|
+
|
15
|
+
## [Version 2.0.1 (2025-03-11)][v2.0.1]
|
16
|
+
|
17
|
+
### Fixed
|
18
|
+
|
19
|
+
- Do not crash with empty IMAP mailboxes.
|
20
|
+
|
8
21
|
## [Version 2.0.0 (2025-03-10)][v2.0.0]
|
9
22
|
|
10
23
|
### Added
|
@@ -66,6 +79,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
66
79
|
- Fix: Docker image.
|
67
80
|
- Fix: Options error message.
|
68
81
|
|
82
|
+
[v2.1.0]: https://github.com/bovender/imapcli/releases/tag/v2.1.0
|
83
|
+
[v2.0.1]: https://github.com/bovender/imapcli/releases/tag/v2.0.1
|
69
84
|
[v2.0.0]: https://github.com/bovender/imapcli/releases/tag/v2.0.0
|
70
85
|
[v1.0.7]: https://github.com/bovender/imapcli/releases/tag/v1.0.7
|
71
86
|
[v1.0.5]: https://github.com/bovender/imapcli/releases/tag/v1.0.5
|
data/Dockerfile
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
FROM ruby:alpine
|
1
|
+
FROM ruby:3.4-alpine
|
2
2
|
LABEL maintainer="bovender@bovender.de"
|
3
3
|
LABEL description="Command-line tool to query IMAP servers, collect stats etc."
|
4
4
|
|
@@ -7,11 +7,12 @@ ADD . /imapcli
|
|
7
7
|
RUN apk add --no-cache git build-base && \
|
8
8
|
gem update --system && \
|
9
9
|
cd imapcli && \
|
10
|
-
|
10
|
+
bundle config set --local without development test && \
|
11
|
+
bundle config set --local deployment true && \
|
11
12
|
bundle install && \
|
12
13
|
apk del build-base && \
|
13
14
|
rm -rf /var/cache/apk/*
|
14
15
|
|
15
16
|
WORKDIR /imapcli
|
16
|
-
ENTRYPOINT ["exe/imapcli"]
|
17
|
+
ENTRYPOINT ["bundle", "exec", "exe/imapcli"]
|
17
18
|
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
imapcli (
|
4
|
+
imapcli (2.0.1)
|
5
5
|
activesupport (~> 8.0)
|
6
6
|
csv
|
7
7
|
descriptive_statistics (~> 2.5)
|
@@ -16,7 +16,7 @@ PATH
|
|
16
16
|
GEM
|
17
17
|
remote: https://rubygems.org/
|
18
18
|
specs:
|
19
|
-
activesupport (8.0.
|
19
|
+
activesupport (8.0.2)
|
20
20
|
base64
|
21
21
|
benchmark (>= 0.3)
|
22
22
|
bigdecimal
|
@@ -83,7 +83,7 @@ GEM
|
|
83
83
|
pp (>= 0.6.0)
|
84
84
|
rdoc (>= 4.0.0)
|
85
85
|
reline (>= 0.4.2)
|
86
|
-
json (2.10.
|
86
|
+
json (2.10.2)
|
87
87
|
language_server-protocol (3.17.0.4)
|
88
88
|
lint_roller (1.1.0)
|
89
89
|
listen (3.9.0)
|
@@ -92,7 +92,7 @@ GEM
|
|
92
92
|
logger (1.6.6)
|
93
93
|
lumberjack (1.2.10)
|
94
94
|
method_source (1.1.0)
|
95
|
-
minitest (5.25.
|
95
|
+
minitest (5.25.5)
|
96
96
|
nenv (0.3.0)
|
97
97
|
net-imap (0.5.6)
|
98
98
|
date
|
@@ -142,7 +142,7 @@ GEM
|
|
142
142
|
diff-lcs (>= 1.2.0, < 2.0)
|
143
143
|
rspec-support (~> 3.13.0)
|
144
144
|
rspec-support (3.13.2)
|
145
|
-
rubocop (1.
|
145
|
+
rubocop (1.74.0)
|
146
146
|
json (~> 2.3)
|
147
147
|
language_server-protocol (~> 3.17.0.2)
|
148
148
|
lint_roller (~> 1.1.0)
|
@@ -153,7 +153,7 @@ GEM
|
|
153
153
|
rubocop-ast (>= 1.38.0, < 2.0)
|
154
154
|
ruby-progressbar (~> 1.7)
|
155
155
|
unicode-display_width (>= 2.4.0, < 4.0)
|
156
|
-
rubocop-ast (1.
|
156
|
+
rubocop-ast (1.39.0)
|
157
157
|
parser (>= 3.3.1.0)
|
158
158
|
rubocop-performance (1.24.0)
|
159
159
|
lint_roller (~> 1.1)
|
@@ -237,4 +237,4 @@ DEPENDENCIES
|
|
237
237
|
simplecov
|
238
238
|
|
239
239
|
BUNDLED WITH
|
240
|
-
2.5
|
240
|
+
2.6.5
|
data/README.md
CHANGED
@@ -15,7 +15,6 @@ IMAP mailbox sizes.
|
|
15
15
|
- [Terminology](#terminology)
|
16
16
|
- [Usage](#usage)
|
17
17
|
- [Alternative resources](#alternative-resources)
|
18
|
-
- [State of the project](#state-of-the-project)
|
19
18
|
- [Credits](#credits)
|
20
19
|
- [License](#license)
|
21
20
|
|
@@ -264,7 +263,18 @@ keyword, as shown below.
|
|
264
263
|
|
265
264
|
Example:
|
266
265
|
|
267
|
-
|
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
|
268
278
|
|
269
279
|
#### Obtaining comma-separated values (CSV)
|
270
280
|
|
@@ -292,20 +302,13 @@ following:
|
|
292
302
|
|
293
303
|
Pull down e-mails from an IMAP server to your local disk
|
294
304
|
|
295
|
-
## State of the project
|
296
|
-
|
297
|
-
I have not been able to work on this project for quite some time. It still
|
298
|
-
serves me well when I occasionally need it. Pull requests are of course welcome.
|
299
|
-
|
300
|
-
This project is [semantically versioned](https://semver.org).
|
301
|
-
|
302
305
|
### To do
|
303
306
|
|
304
|
-
[x] More human-friendly number formatting (e.g., MiB/GiB as appropriate)
|
305
|
-
[ ] Output to file
|
306
|
-
[ ] Deal with server-specific mailbox separator characters (e.g. '.' vs. '/')
|
307
|
-
[ ] Man page
|
308
|
-
[ ] More commands?
|
307
|
+
- [x] More human-friendly number formatting (e.g., MiB/GiB as appropriate)
|
308
|
+
- [ ] Output to file
|
309
|
+
- [ ] Deal with server-specific mailbox separator characters (e.g. '.' vs. '/')
|
310
|
+
- [ ] Man page
|
311
|
+
- [ ] More commands?
|
309
312
|
|
310
313
|
## Credits
|
311
314
|
|
data/imapcli.gemspec
CHANGED
@@ -22,10 +22,10 @@ Gem::Specification.new do |s|
|
|
22
22
|
s.bindir = 'exe'
|
23
23
|
s.executables = ['imapcli']
|
24
24
|
|
25
|
+
s.add_dependency('activesupport', '~> 8.0')
|
25
26
|
s.add_dependency('csv')
|
26
27
|
s.add_dependency('descriptive_statistics', '~> 2.5')
|
27
28
|
s.add_dependency('dotenv', '~> 3.1')
|
28
|
-
s.add_dependency('activesupport', '~> 8.0')
|
29
29
|
s.add_dependency('gli', '~> 2.22')
|
30
30
|
s.add_dependency('net-imap')
|
31
31
|
s.add_dependency('tty-progressbar', '~> 0.18')
|
data/lib/imapcli/cli.rb
CHANGED
@@ -74,6 +74,10 @@ module Imapcli
|
|
74
74
|
default: 'total_size'
|
75
75
|
c.switch %i[reverse], desc: 'Reverse sort order (largest first)', negatable: false
|
76
76
|
c.switch [:csv], desc: 'Output comma-separated values (CSV)'
|
77
|
+
c.flag %i[l limit],
|
78
|
+
desc: 'Limit the results to n folders (IMAP mailboxes)',
|
79
|
+
arg_name: 'max_lines',
|
80
|
+
default: 10
|
77
81
|
|
78
82
|
c.action do |_global_options, options, args| # rubocop:disable Metrics/BlockLength
|
79
83
|
raise unless @validator.stats_options_valid?(options, args)
|
data/lib/imapcli/command.rb
CHANGED
@@ -68,7 +68,12 @@ module Imapcli
|
|
68
68
|
yield current_count if block_given?
|
69
69
|
end
|
70
70
|
end
|
71
|
-
|
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
|
@@ -138,28 +143,30 @@ module Imapcli
|
|
138
143
|
end
|
139
144
|
end
|
140
145
|
|
141
|
-
def sorted_list(list, options
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
146
|
+
def sorted_list(list, options)
|
147
|
+
return list if options.nil? || options[:sort].nil?
|
148
|
+
sort_property = options[:sort]&.to_sym
|
149
|
+
if %i[count total_size min_size q1 median_size q3 max_size].include? sort_property
|
150
|
+
sorted = sort_mailboxes(list, sort_property, options[:reverse])
|
151
|
+
else
|
152
|
+
raise "invalid sort option: #{options[:sort]}"
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
private
|
157
|
+
|
158
|
+
# Sorts a list of mailboxes according to a given property
|
159
|
+
# such as total count of e-mails, total size of all e-mails etc.
|
160
|
+
# Some mailboxes may not have this property, e.g., an empty
|
161
|
+
# mailbox will have `nil` values for median_size etc.
|
162
|
+
# We always sort mailboxes with `nil` values to the bottom.
|
163
|
+
def sort_mailboxes(mailboxes, property, reverse)
|
164
|
+
# Devide ary into two parts, one part where the property has a value
|
165
|
+
# and the other where the property is nil.
|
166
|
+
partitions = mailboxes.partition { |mailbox| mailbox.stats.send(property) }
|
167
|
+
sorted = partitions[0].sort_by { |mailbox| mailbox.stats.send(property) }
|
168
|
+
sorted = sorted.reverse if reverse
|
169
|
+
sorted + partitions[1]
|
163
170
|
end
|
164
171
|
|
165
172
|
end
|
data/lib/imapcli/version.rb
CHANGED
@@ -22,9 +22,11 @@ RSpec.describe Imapcli::Command do
|
|
22
22
|
Net::IMAP::MailboxList.new(nil, '/', 'Inbox'),
|
23
23
|
Net::IMAP::MailboxList.new(nil, '/', 'Inbox/Foo'),
|
24
24
|
Net::IMAP::MailboxList.new(nil, '/', 'Inbox/Foo/Sub'),
|
25
|
+
Net::IMAP::MailboxList.new(nil, '/', 'Inbox/Many/Messages'),
|
25
26
|
Net::IMAP::MailboxList.new(nil, '/', 'Inbox/Bar'),
|
26
|
-
Net::IMAP::MailboxList.new(nil, '/', 'Inbox/
|
27
|
-
Net::IMAP::MailboxList.new(nil, '/', 'Inbox/
|
27
|
+
Net::IMAP::MailboxList.new(nil, '/', 'Inbox/Largest/Messages'),
|
28
|
+
Net::IMAP::MailboxList.new(nil, '/', 'Inbox/Smallest/Messages'),
|
29
|
+
Net::IMAP::MailboxList.new(nil, '/', 'Inbox/Empty'),
|
28
30
|
])
|
29
31
|
end
|
30
32
|
|
@@ -57,17 +59,19 @@ RSpec.describe Imapcli::Command do
|
|
57
59
|
allow(client).to receive(:message_sizes) do |mailbox|
|
58
60
|
case mailbox
|
59
61
|
when 'Inbox'
|
60
|
-
|
62
|
+
[200, 300, 400, 500]
|
61
63
|
when 'Inbox/Foo'
|
62
|
-
|
63
|
-
when 'Inbox/
|
64
|
-
Array.new(10,
|
64
|
+
[355, 360, 380, 200]
|
65
|
+
when 'Inbox/Many/Messages'
|
66
|
+
Array.new(10, 100)
|
65
67
|
when 'Inbox/Bar'
|
66
|
-
|
67
|
-
when 'Inbox/
|
68
|
-
[
|
69
|
-
when 'Inbox/
|
70
|
-
|
68
|
+
[2_000, 3_000, 4_000]
|
69
|
+
when 'Inbox/Largest/Messages'
|
70
|
+
[2_500_000, 100_000, 200_000]
|
71
|
+
when 'Inbox/Smallest/Messages'
|
72
|
+
[10, 20, 30]
|
73
|
+
when 'Inbox/Empty'
|
74
|
+
[]
|
71
75
|
else
|
72
76
|
[1024, 2048, 4096, 8192]
|
73
77
|
end
|
@@ -77,8 +81,8 @@ RSpec.describe Imapcli::Command do
|
|
77
81
|
it 'for all folders' do
|
78
82
|
output = command.stats
|
79
83
|
expect(output).to be_a Array
|
80
|
-
expect(output.length).to eq
|
81
|
-
expect(output[0][0]).to eq 'Inbox'
|
84
|
+
expect(output.length).to eq 9
|
85
|
+
expect(output[0][0]).to eq 'Inbox' # sorted before 'Inbox'
|
82
86
|
expect(output[0][1]).to eq 4
|
83
87
|
end
|
84
88
|
|
@@ -87,7 +91,15 @@ RSpec.describe Imapcli::Command do
|
|
87
91
|
expect(output).to be_a Array
|
88
92
|
expect(output.length).to eq 1
|
89
93
|
expect(output[0][0]).to eq 'Inbox/Foo'
|
90
|
-
expect(output[0][1]).to eq
|
94
|
+
expect(output[0][1]).to eq 4 # depends on message_sizes stub (see above)
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'for an empty folder' do
|
98
|
+
output = command.stats('Inbox/Empty')
|
99
|
+
expect(output).to be_a Array
|
100
|
+
expect(output.length).to eq 1
|
101
|
+
expect(output[0][0]).to eq 'Inbox/Empty'
|
102
|
+
expect(output[0][1]).to eq 0
|
91
103
|
end
|
92
104
|
|
93
105
|
it 'for a given folder and subfolders' do
|
@@ -100,26 +112,31 @@ RSpec.describe Imapcli::Command do
|
|
100
112
|
it 'sorts by number of messages' do
|
101
113
|
output = command.stats('Inbox', depth: -1, sort: :count, reverse: true)
|
102
114
|
expect(output).to be_a Array
|
103
|
-
expect(output[0][0]).to eq 'Inbox/
|
115
|
+
expect(output[0][0]).to eq 'Inbox/Many/Messages'
|
104
116
|
end
|
105
117
|
|
106
118
|
it 'sorts by total message size' do
|
107
|
-
output = command.stats('Inbox', depth: -1, sort: :total_size, reverse:
|
119
|
+
output = command.stats('Inbox', depth: -1, sort: :total_size, reverse: false)
|
108
120
|
expect(output).to be_a Array
|
109
|
-
expect(output[
|
121
|
+
expect(output[-2][0]).to eq 'Inbox/Largest/Messages'
|
110
122
|
end
|
111
123
|
|
112
|
-
it 'sorts
|
124
|
+
it 'reverse-sorts largest message first' do
|
113
125
|
output = command.stats('Inbox', depth: -1, sort: :max_size, reverse: true)
|
114
126
|
expect(output).to be_a Array
|
115
|
-
expect(output[0][0]).to eq 'Inbox/
|
127
|
+
expect(output[0][0]).to eq 'Inbox/Largest/Messages'
|
128
|
+
expect(output[-2][0]).to eq 'Inbox/Empty'
|
116
129
|
end
|
117
130
|
|
118
|
-
it 'sorts
|
119
|
-
output = command.stats('Inbox', depth: -1, sort: :min_size, reverse:
|
131
|
+
it 'sorts smallest message first' do
|
132
|
+
output = command.stats('Inbox', depth: -1, sort: :min_size, reverse: false)
|
120
133
|
expect(output).to be_a Array
|
121
|
-
|
122
|
-
|
134
|
+
expect(output[0][0]).to eq 'Inbox/Smallest/Messages'
|
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
|
123
140
|
end
|
124
141
|
end
|
125
142
|
|
metadata
CHANGED
@@ -1,15 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: imapcli
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Daniel Kraus (bovender)
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-03-
|
11
|
+
date: 2025-03-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activesupport
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '8.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '8.0'
|
13
27
|
- !ruby/object:Gem::Dependency
|
14
28
|
name: csv
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -52,20 +66,6 @@ dependencies:
|
|
52
66
|
- - "~>"
|
53
67
|
- !ruby/object:Gem::Version
|
54
68
|
version: '3.1'
|
55
|
-
- !ruby/object:Gem::Dependency
|
56
|
-
name: activesupport
|
57
|
-
requirement: !ruby/object:Gem::Requirement
|
58
|
-
requirements:
|
59
|
-
- - "~>"
|
60
|
-
- !ruby/object:Gem::Version
|
61
|
-
version: '8.0'
|
62
|
-
type: :runtime
|
63
|
-
prerelease: false
|
64
|
-
version_requirements: !ruby/object:Gem::Requirement
|
65
|
-
requirements:
|
66
|
-
- - "~>"
|
67
|
-
- !ruby/object:Gem::Version
|
68
|
-
version: '8.0'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: gli
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|