imapcli 1.0.7 → 2.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 +4 -4
- data/.devcontainer/devcontainer.json +22 -0
- data/.github/dependabot.yml +12 -0
- data/.github/workflows/ci.yml +57 -0
- data/.gitignore +3 -0
- data/.rubocop.yml +53 -0
- data/.vscode/launch.json +59 -0
- data/CHANGELOG.md +54 -5
- data/Dockerfile +8 -6
- data/Gemfile +13 -4
- data/Gemfile.lock +165 -41
- data/Guardfile +8 -6
- data/README.md +50 -39
- data/Rakefile +5 -7
- data/bin/bundle +109 -0
- data/bin/rspec +27 -0
- data/bin/rubocop +27 -0
- data/exe/imapcli +8 -0
- data/imapcli.gemspec +26 -18
- data/lib/imapcli/cli.rb +180 -0
- data/lib/imapcli/client.rb +32 -32
- data/lib/imapcli/command.rb +44 -44
- data/lib/imapcli/mailbox.rb +34 -38
- data/lib/imapcli/option_validator.rb +6 -6
- data/lib/imapcli/stats.rb +9 -11
- data/lib/imapcli/version.rb +3 -1
- data/lib/imapcli.rb +20 -6
- data/spec/lib/imapcli/client_spec.rb +34 -22
- data/spec/lib/imapcli/command_spec.rb +35 -31
- data/spec/lib/imapcli/mailbox_spec.rb +36 -23
- data/spec/lib/imapcli/stats_spec.rb +18 -16
- data/spec/spec_helper.rb +22 -70
- metadata +63 -43
- data/.rspec +0 -1
- data/bin/imapcli +0 -164
- data/spec/lib/imapcli/option_validator_spec.rb +0 -4
data/README.md
CHANGED
@@ -9,15 +9,15 @@ IMAP mailbox sizes.
|
|
9
9
|
|
10
10
|
## Table of contents
|
11
11
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
12
|
+
- [Motivation](#motivation)
|
13
|
+
- [Warning](#warning)
|
14
|
+
- [Installing and executing `imapcli`](#installing-and-executing-imapcli)
|
15
|
+
- [Terminology](#terminology)
|
16
|
+
- [Usage](#usage)
|
17
|
+
- [Alternative resources](#alternative-resources)
|
18
|
+
- [State of the project](#state-of-the-project)
|
19
|
+
- [Credits](#credits)
|
20
|
+
- [License](#license)
|
21
21
|
|
22
22
|
## Motivation
|
23
23
|
|
@@ -83,7 +83,7 @@ Install:
|
|
83
83
|
Run:
|
84
84
|
|
85
85
|
cd imapcli
|
86
|
-
bundle exec
|
86
|
+
bundle exec exe/imapcli
|
87
87
|
|
88
88
|
### Install the gem
|
89
89
|
|
@@ -106,11 +106,11 @@ smaller).
|
|
106
106
|
|
107
107
|
Run:
|
108
108
|
|
109
|
-
docker run -it bovender/imapcli <arguments>
|
109
|
+
docker run -it --rm bovender/imapcli <arguments>
|
110
110
|
|
111
111
|
Example:
|
112
112
|
|
113
|
-
docker run -it bovender/imapcli -s myserver.example.com -u user -P info
|
113
|
+
docker run -it --rm bovender/imapcli -s myserver.example.com -u user -P info
|
114
114
|
|
115
115
|
The Docker repository is at <https://hub.docker.com/r/bovender/imapcli>.
|
116
116
|
|
@@ -124,7 +124,7 @@ have their mails organized in **folders**; in IMAP speak, a folder is a **maibox
|
|
124
124
|
For basic usage instructions and possible options, run `imapcli` and examine
|
125
125
|
the output. Please note that `imapcli` distinguishes between global and
|
126
126
|
command-specific options. Global options *precede* and command-specific options
|
127
|
-
|
127
|
+
-follow* a `command`, see the output of `imapcli` (without command or options)
|
128
128
|
for more information.
|
129
129
|
|
130
130
|
Note: The following examples use the command `imapcli`. Depending on how you
|
@@ -187,13 +187,15 @@ of messages in them, this may take a little while.
|
|
187
187
|
|
188
188
|
`imapcli` prints the following statistics about the message sizes in a mailbox:
|
189
189
|
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
190
|
+
- `Count`: Number of individual messages
|
191
|
+
- `Total size`: Total size of all messages in the mailbox
|
192
|
+
- `Min`: Size of the smallest message in the mailbox
|
193
|
+
- `Q1`: First quartile of message sizes in the mailbox
|
194
|
+
- `Median`: Median of all message sizes in the mailbox
|
195
|
+
- `Q3`: Third quartile of message sizes in the mailbox
|
196
|
+
- `Max`: Size of the largest message in the mailbox
|
197
|
+
|
198
|
+
Use the `-H` or `--human` switch to output the message sizes with SI prefixes.
|
197
199
|
|
198
200
|
#### All mailboxes
|
199
201
|
|
@@ -245,13 +247,20 @@ Use the `-r`/`--recurse` flag:
|
|
245
247
|
By default, mailboxes are sorted alphabetically. To sort by a specific statistic,
|
246
248
|
use an `-o`/`--sort` option:
|
247
249
|
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
250
|
+
- `-o count`
|
251
|
+
- `-o total_size`
|
252
|
+
- `-o min_size`
|
253
|
+
- `-o q1_size`
|
254
|
+
- `-o median_size`
|
255
|
+
- `-o q3_size`
|
256
|
+
- `-o max_size`
|
257
|
+
|
258
|
+
This will sort the output from smallest to largest (ascending).
|
259
|
+
|
260
|
+
Use the `--reverse` switch to sort from largest to smallest (descending).
|
261
|
+
|
262
|
+
Note that these are options to the `stats` command and need to come after the `stats`
|
263
|
+
keyword, as shown below.
|
255
264
|
|
256
265
|
Example:
|
257
266
|
|
@@ -268,18 +277,18 @@ following:
|
|
268
277
|
|
269
278
|
### IMAP folder size script
|
270
279
|
|
271
|
-
|
280
|
+
- <https://code.iamcal.com/pl/imap_folders>
|
272
281
|
|
273
282
|
Ad-hoc perl script that computes the sizes of each mailbox. `imapcli` was
|
274
283
|
inspired by this!
|
275
284
|
|
276
285
|
### IMAP synchronization and backup tools
|
277
286
|
|
278
|
-
|
287
|
+
- <https://github.com/OfflineIMAP/imapfw>
|
279
288
|
|
280
289
|
Framework to work with mails
|
281
290
|
|
282
|
-
|
291
|
+
- <https://github.com/polo2ro/imapbox>
|
283
292
|
|
284
293
|
Pull down e-mails from an IMAP server to your local disk
|
285
294
|
|
@@ -288,18 +297,15 @@ following:
|
|
288
297
|
I have not been able to work on this project for quite some time. It still
|
289
298
|
serves me well when I occasionally need it. Pull requests are of course welcome.
|
290
299
|
|
291
|
-
I've decided to have one `main` branch, and to get rid of the `master` and
|
292
|
-
`
|
293
|
-
|
294
300
|
This project is [semantically versioned](https://semver.org).
|
295
301
|
|
296
302
|
### To do
|
297
303
|
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
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?
|
303
309
|
|
304
310
|
## Credits
|
305
311
|
|
@@ -308,9 +314,14 @@ gem by [David Copeland](https://github.com/davetron5000) and makes extensive use
|
|
308
314
|
of [Piotr Murach's](https://github.com/piotrmurach) excellent `TTY` tools. See
|
309
315
|
the `Gemfile` for other work that this tool depends on.
|
310
316
|
|
317
|
+
## Contributors
|
318
|
+
|
319
|
+
- [@bovender](https://github.com/bovender)
|
320
|
+
- [@n-rodriguez](https://github.com/n-rodriguez)
|
321
|
+
|
311
322
|
## License
|
312
323
|
|
313
|
-
© 2017
|
324
|
+
© 2017-2025 Daniel Kraus (@bovender)
|
314
325
|
|
315
326
|
Licensed under the Apache License, Version 2.0 (the "License");
|
316
327
|
you may not use this file except in compliance with the License.
|
data/Rakefile
CHANGED
@@ -1,20 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'rake/clean'
|
2
4
|
require 'rubygems'
|
3
5
|
require 'rubygems/package_task'
|
4
6
|
require 'rdoc/task'
|
5
7
|
# require 'cucumber'
|
6
8
|
# require 'cucumber/rake/task'
|
9
|
+
|
7
10
|
Rake::RDocTask.new do |rd|
|
8
|
-
rd.main =
|
9
|
-
rd.rdoc_files.include(
|
11
|
+
rd.main = 'README.md'
|
12
|
+
rd.rdoc_files.include('README.md', 'lib/**/*.rb', 'bin/**/*')
|
10
13
|
rd.title = 'imapcli'
|
11
14
|
end
|
12
15
|
|
13
|
-
spec = eval(File.read('imapcli.gemspec'))
|
14
|
-
|
15
|
-
Gem::PackageTask.new(spec) do |pkg|
|
16
|
-
end
|
17
|
-
|
18
16
|
# CUKE_RESULTS = 'results.html'
|
19
17
|
# CLEAN << CUKE_RESULTS
|
20
18
|
# desc 'Run features'
|
data/bin/bundle
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
#
|
5
|
+
# This file was generated by Bundler.
|
6
|
+
#
|
7
|
+
# The application 'bundle' is installed as part of a gem, and
|
8
|
+
# this file is here to facilitate running it.
|
9
|
+
#
|
10
|
+
|
11
|
+
require "rubygems"
|
12
|
+
|
13
|
+
m = Module.new do
|
14
|
+
module_function
|
15
|
+
|
16
|
+
def invoked_as_script?
|
17
|
+
File.expand_path($0) == File.expand_path(__FILE__)
|
18
|
+
end
|
19
|
+
|
20
|
+
def env_var_version
|
21
|
+
ENV["BUNDLER_VERSION"]
|
22
|
+
end
|
23
|
+
|
24
|
+
def cli_arg_version
|
25
|
+
return unless invoked_as_script? # don't want to hijack other binstubs
|
26
|
+
return unless "update".start_with?(ARGV.first || " ") # must be running `bundle update`
|
27
|
+
bundler_version = nil
|
28
|
+
update_index = nil
|
29
|
+
ARGV.each_with_index do |a, i|
|
30
|
+
if update_index && update_index.succ == i && a.match?(Gem::Version::ANCHORED_VERSION_PATTERN)
|
31
|
+
bundler_version = a
|
32
|
+
end
|
33
|
+
next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/
|
34
|
+
bundler_version = $1
|
35
|
+
update_index = i
|
36
|
+
end
|
37
|
+
bundler_version
|
38
|
+
end
|
39
|
+
|
40
|
+
def gemfile
|
41
|
+
gemfile = ENV["BUNDLE_GEMFILE"]
|
42
|
+
return gemfile if gemfile && !gemfile.empty?
|
43
|
+
|
44
|
+
File.expand_path("../Gemfile", __dir__)
|
45
|
+
end
|
46
|
+
|
47
|
+
def lockfile
|
48
|
+
lockfile =
|
49
|
+
case File.basename(gemfile)
|
50
|
+
when "gems.rb" then gemfile.sub(/\.rb$/, ".locked")
|
51
|
+
else "#{gemfile}.lock"
|
52
|
+
end
|
53
|
+
File.expand_path(lockfile)
|
54
|
+
end
|
55
|
+
|
56
|
+
def lockfile_version
|
57
|
+
return unless File.file?(lockfile)
|
58
|
+
lockfile_contents = File.read(lockfile)
|
59
|
+
return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/
|
60
|
+
Regexp.last_match(1)
|
61
|
+
end
|
62
|
+
|
63
|
+
def bundler_requirement
|
64
|
+
@bundler_requirement ||=
|
65
|
+
env_var_version ||
|
66
|
+
cli_arg_version ||
|
67
|
+
bundler_requirement_for(lockfile_version)
|
68
|
+
end
|
69
|
+
|
70
|
+
def bundler_requirement_for(version)
|
71
|
+
return "#{Gem::Requirement.default}.a" unless version
|
72
|
+
|
73
|
+
bundler_gem_version = Gem::Version.new(version)
|
74
|
+
|
75
|
+
bundler_gem_version.approximate_recommendation
|
76
|
+
end
|
77
|
+
|
78
|
+
def load_bundler!
|
79
|
+
ENV["BUNDLE_GEMFILE"] ||= gemfile
|
80
|
+
|
81
|
+
activate_bundler
|
82
|
+
end
|
83
|
+
|
84
|
+
def activate_bundler
|
85
|
+
gem_error = activation_error_handling do
|
86
|
+
gem "bundler", bundler_requirement
|
87
|
+
end
|
88
|
+
return if gem_error.nil?
|
89
|
+
require_error = activation_error_handling do
|
90
|
+
require "bundler/version"
|
91
|
+
end
|
92
|
+
return if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION))
|
93
|
+
warn "Activating bundler (#{bundler_requirement}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_requirement}'`"
|
94
|
+
exit 42
|
95
|
+
end
|
96
|
+
|
97
|
+
def activation_error_handling
|
98
|
+
yield
|
99
|
+
nil
|
100
|
+
rescue StandardError, LoadError => e
|
101
|
+
e
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
m.load_bundler!
|
106
|
+
|
107
|
+
if m.invoked_as_script?
|
108
|
+
load Gem.bin_path("bundler", "bundle")
|
109
|
+
end
|
data/bin/rspec
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
#
|
5
|
+
# This file was generated by Bundler.
|
6
|
+
#
|
7
|
+
# The application 'rspec' is installed as part of a gem, and
|
8
|
+
# this file is here to facilitate running it.
|
9
|
+
#
|
10
|
+
|
11
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
|
12
|
+
|
13
|
+
bundle_binstub = File.expand_path("bundle", __dir__)
|
14
|
+
|
15
|
+
if File.file?(bundle_binstub)
|
16
|
+
if File.read(bundle_binstub, 300).include?("This file was generated by Bundler")
|
17
|
+
load(bundle_binstub)
|
18
|
+
else
|
19
|
+
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
20
|
+
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
require "rubygems"
|
25
|
+
require "bundler/setup"
|
26
|
+
|
27
|
+
load Gem.bin_path("rspec-core", "rspec")
|
data/bin/rubocop
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
#
|
5
|
+
# This file was generated by Bundler.
|
6
|
+
#
|
7
|
+
# The application 'rubocop' is installed as part of a gem, and
|
8
|
+
# this file is here to facilitate running it.
|
9
|
+
#
|
10
|
+
|
11
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
|
12
|
+
|
13
|
+
bundle_binstub = File.expand_path('bundle', __dir__)
|
14
|
+
|
15
|
+
if File.file?(bundle_binstub)
|
16
|
+
if File.read(bundle_binstub, 300).include?('This file was generated by Bundler')
|
17
|
+
load(bundle_binstub)
|
18
|
+
else
|
19
|
+
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
20
|
+
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
require 'rubygems'
|
25
|
+
require 'bundler/setup'
|
26
|
+
|
27
|
+
load Gem.bin_path('rubocop', 'rubocop')
|
data/exe/imapcli
ADDED
data/imapcli.gemspec
CHANGED
@@ -1,27 +1,35 @@
|
|
1
|
-
#
|
2
|
-
|
3
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'lib/imapcli/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
4
6
|
s.name = 'imapcli'
|
5
7
|
s.version = Imapcli::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
6
9
|
s.author = 'Daniel Kraus (bovender)'
|
7
10
|
s.email = 'bovender@bovender.de'
|
8
11
|
s.homepage = 'https://github.com/bovender/imapcli'
|
9
|
-
s.license = 'Apache-2.0'
|
10
|
-
s.platform = Gem::Platform::RUBY
|
11
12
|
s.summary = 'Command-line tool to query IMAP servers'
|
13
|
+
s.license = 'Apache-2.0'
|
14
|
+
|
15
|
+
s.required_ruby_version = '>= 3.1.0'
|
16
|
+
|
12
17
|
s.files = `git ls-files`.split("\n")
|
13
|
-
|
14
|
-
s.extra_rdoc_files = ['README.md','imapcli.rdoc']
|
18
|
+
|
19
|
+
s.extra_rdoc_files = ['README.md', 'imapcli.rdoc']
|
15
20
|
s.rdoc_options << '--title' << 'imapcli' << '--main' << 'README.md' << '-ri'
|
16
|
-
|
17
|
-
s.
|
18
|
-
s.
|
19
|
-
|
20
|
-
s.
|
21
|
-
s.
|
22
|
-
s.
|
23
|
-
s.
|
24
|
-
s.
|
25
|
-
s.
|
26
|
-
s.
|
21
|
+
|
22
|
+
s.bindir = 'exe'
|
23
|
+
s.executables = ['imapcli']
|
24
|
+
|
25
|
+
s.add_dependency('csv')
|
26
|
+
s.add_dependency('descriptive_statistics', '~> 2.5')
|
27
|
+
s.add_dependency('dotenv', '~> 3.1')
|
28
|
+
s.add_dependency('activesupport', '~> 8.0')
|
29
|
+
s.add_dependency('gli', '~> 2.22')
|
30
|
+
s.add_dependency('net-imap')
|
31
|
+
s.add_dependency('tty-progressbar', '~> 0.18')
|
32
|
+
s.add_dependency('tty-prompt', '~> 0.23')
|
33
|
+
s.add_dependency('tty-table', '~> 0.12')
|
34
|
+
s.add_dependency('zeitwerk', '~> 2.7.0')
|
27
35
|
end
|
data/lib/imapcli/cli.rb
ADDED
@@ -0,0 +1,180 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Imapcli
|
4
|
+
class Cli # rubocop:disable Metrics/ClassLength,Style/Documentation
|
5
|
+
extend GLI::App
|
6
|
+
|
7
|
+
program_desc 'Command-line interface for IMAP servers'
|
8
|
+
|
9
|
+
version Imapcli::VERSION
|
10
|
+
|
11
|
+
subcommand_option_handling :normal
|
12
|
+
arguments :strict
|
13
|
+
sort_help :manually
|
14
|
+
wrap_help_text :tty_only
|
15
|
+
|
16
|
+
desc 'Domain name (FQDN) of the IMAP server'
|
17
|
+
default_value ENV.fetch('IMAP_SERVER', nil)
|
18
|
+
arg_name 'imap.example.com'
|
19
|
+
flag %i[s server]
|
20
|
+
|
21
|
+
desc 'Log-in name (username/email)'
|
22
|
+
default_value ENV.fetch('IMAP_USER', nil)
|
23
|
+
arg_name 'user'
|
24
|
+
flag %i[u user]
|
25
|
+
|
26
|
+
desc 'Log-in password'
|
27
|
+
# default_value ENV['IMAP_PASS']
|
28
|
+
arg_name 'password'
|
29
|
+
flag %i[p password]
|
30
|
+
|
31
|
+
desc 'Prompt for password'
|
32
|
+
switch %i[P prompt], negatable: false
|
33
|
+
|
34
|
+
desc 'Verbose output (e.g., response values from Rubys Net::IMAP)'
|
35
|
+
switch %i[v verbose], negatable: false
|
36
|
+
|
37
|
+
desc 'Tests if the server is available and log-in succeeds with the credentials'
|
38
|
+
command :check do |c|
|
39
|
+
c.action do |_global_options, _options, _args|
|
40
|
+
@command.check ? @prompt.ok('login successful') : @prompt.error('login failed')
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
desc 'Prints information about the server'
|
45
|
+
command :info do |c|
|
46
|
+
c.action do |_global_options, _options, _args|
|
47
|
+
@command.info.each { |line| @prompt.say line }
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
desc 'Lists mailboxes (folders)'
|
52
|
+
command :list do |c|
|
53
|
+
c.action do |_global_options, _options, _args|
|
54
|
+
@command.list.each { |line| @prompt.say line }
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
desc 'Collects mailbox statistics'
|
59
|
+
arg_name :mailbox, optional: true, multiple: true
|
60
|
+
command :stats do |c| # rubocop:disable Metrics/BlockLength
|
61
|
+
c.switch %i[r recurse],
|
62
|
+
desc: 'Recurse into sub mailboxes',
|
63
|
+
negatable: false
|
64
|
+
c.switch %i[R no_recurse],
|
65
|
+
desc: 'Do not recurse into sub mailboxes',
|
66
|
+
negatable: false
|
67
|
+
c.switch %i[H human],
|
68
|
+
desc: 'Convert byte counts to human-friendly formats',
|
69
|
+
negatable: false
|
70
|
+
c.flag %i[o sort],
|
71
|
+
desc: 'Ordered (sorted) results',
|
72
|
+
arg_name: 'sort_property',
|
73
|
+
must_match: %w[count total_size median_size min_size q1_size q3_size max_size],
|
74
|
+
default: 'total_size'
|
75
|
+
c.switch %i[reverse], desc: 'Reverse sort order (largest first)', negatable: false
|
76
|
+
c.switch [:csv], desc: 'Output comma-separated values (CSV)'
|
77
|
+
|
78
|
+
c.action do |_global_options, options, args| # rubocop:disable Metrics/BlockLength
|
79
|
+
raise unless @validator.stats_options_valid?(options, args)
|
80
|
+
|
81
|
+
progress_bar = nil
|
82
|
+
|
83
|
+
head = ['Mailbox', 'Count', 'Total size', 'Min', 'Q1', 'Median', 'Q3', 'Max']
|
84
|
+
body = @command.stats(args, options) do |n|
|
85
|
+
if progress_bar
|
86
|
+
progress_bar.advance
|
87
|
+
else
|
88
|
+
@prompt.say "info: collecting stats for #{n} folders" if n > 1
|
89
|
+
progress_bar = TTY::ProgressBar.new(
|
90
|
+
'collecting stats... :current/:total (:percent, :eta remaining)',
|
91
|
+
total: n, clear: true
|
92
|
+
)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
formatted_body = body.map do |row|
|
96
|
+
row[0..1] + row[2..].map { |cell| format_bytes(cell, options[:human]) }
|
97
|
+
end
|
98
|
+
|
99
|
+
if options[:csv]
|
100
|
+
unless options[:human]
|
101
|
+
@prompt.warn 'notice: BREAKING CHANGE IN VERSION 2: messages sizes in CSV output are now given in bytes, not kiB'
|
102
|
+
end
|
103
|
+
@prompt.say head.to_csv
|
104
|
+
last_mailbox_line = body.length == 1 ? -1 : -2 # skip grand total if present
|
105
|
+
formatted_body[0..last_mailbox_line].each { |row| @prompt.say row.to_csv }
|
106
|
+
else
|
107
|
+
formatted_body = formatted_body.insert(0, :separator).insert(-2, :separator)
|
108
|
+
|
109
|
+
if options[:human]
|
110
|
+
@prompt.say "notice: -H/--human flag present, message sizes are given with SI prefixes"
|
111
|
+
else
|
112
|
+
@prompt.say "notice: message sizes are given in bytes"
|
113
|
+
end
|
114
|
+
|
115
|
+
table = TTY::Table.new(head, formatted_body)
|
116
|
+
rendered_table = table.render(:unicode) do |renderer|
|
117
|
+
renderer.alignments = [:left] + Array.new(7, :right)
|
118
|
+
renderer.border.style = :blue
|
119
|
+
end
|
120
|
+
@prompt.say rendered_table
|
121
|
+
|
122
|
+
# If any unknown mailboxes were requested, print an informative footer
|
123
|
+
if body.any? { |line| line[0].start_with? Imapcli::Command.unknown_mailbox_prefix }
|
124
|
+
@prompt.warn "#{Imapcli::Command.unknown_mailbox_prefix}unknown mailbox"
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def self.format_bytes(bytes, human = false)
|
131
|
+
human ? ActiveSupport::NumberHelper.number_to_human_size(bytes) : bytes
|
132
|
+
end
|
133
|
+
|
134
|
+
pre do |global, _command, _options, _args|
|
135
|
+
@prompt = TTY::Prompt.new
|
136
|
+
@validator = Imapcli::OptionValidator.new
|
137
|
+
raise unless @validator.global_options_valid?(global)
|
138
|
+
|
139
|
+
global[:p] = @prompt.mask 'Enter password:' if global[:P]
|
140
|
+
global[:p] ||= ENV.fetch('IMAP_PASS', nil)
|
141
|
+
|
142
|
+
client = Imapcli::Client.new(global[:s], global[:u], global[:p])
|
143
|
+
@prompt.say "server: #{global[:s]}"
|
144
|
+
@prompt.say "user: #{global[:u]}"
|
145
|
+
raise 'invalid server name' unless client.server_valid?
|
146
|
+
raise 'invalid user name' unless client.user_valid?
|
147
|
+
|
148
|
+
@prompt.warn 'warning: no password was provided (missing -p/-P option)' unless global[:p]
|
149
|
+
raise 'unable to connect to server' unless client.connection
|
150
|
+
|
151
|
+
@command = Imapcli::Command.new(client)
|
152
|
+
|
153
|
+
true
|
154
|
+
end
|
155
|
+
|
156
|
+
post do |global, _command, _options, _args|
|
157
|
+
@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
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
on_error do |exception|
|
167
|
+
@client&.logout
|
168
|
+
if @validator&.errors&.any?
|
169
|
+
@validator.errors.each { |error| @prompt.error error }
|
170
|
+
else
|
171
|
+
@prompt&.error "error: #{exception}"
|
172
|
+
end
|
173
|
+
@prompt.nil? # if we do not have a prompt yet, let GLI handle the exception
|
174
|
+
end
|
175
|
+
|
176
|
+
def print_warnings
|
177
|
+
@validator.warnings.each { |warning| @prompt.warn warning }
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|