imapcli 1.0.7 → 2.0.1
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 +61 -5
- data/Dockerfile +10 -7
- data/Gemfile +13 -4
- data/Gemfile.lock +165 -41
- data/Guardfile +8 -6
- data/README.md +49 -46
- 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 +43 -41
- 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 +63 -47
- 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 +59 -39
- 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,14 @@ IMAP mailbox sizes.
|
|
9
9
|
|
10
10
|
## Table of contents
|
11
11
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
* [License](#license)
|
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
|
+
- [Credits](#credits)
|
19
|
+
- [License](#license)
|
21
20
|
|
22
21
|
## Motivation
|
23
22
|
|
@@ -83,7 +82,7 @@ Install:
|
|
83
82
|
Run:
|
84
83
|
|
85
84
|
cd imapcli
|
86
|
-
bundle exec
|
85
|
+
bundle exec exe/imapcli
|
87
86
|
|
88
87
|
### Install the gem
|
89
88
|
|
@@ -106,11 +105,11 @@ smaller).
|
|
106
105
|
|
107
106
|
Run:
|
108
107
|
|
109
|
-
docker run -it bovender/imapcli <arguments>
|
108
|
+
docker run -it --rm bovender/imapcli <arguments>
|
110
109
|
|
111
110
|
Example:
|
112
111
|
|
113
|
-
docker run -it bovender/imapcli -s myserver.example.com -u user -P info
|
112
|
+
docker run -it --rm bovender/imapcli -s myserver.example.com -u user -P info
|
114
113
|
|
115
114
|
The Docker repository is at <https://hub.docker.com/r/bovender/imapcli>.
|
116
115
|
|
@@ -124,7 +123,7 @@ have their mails organized in **folders**; in IMAP speak, a folder is a **maibox
|
|
124
123
|
For basic usage instructions and possible options, run `imapcli` and examine
|
125
124
|
the output. Please note that `imapcli` distinguishes between global and
|
126
125
|
command-specific options. Global options *precede* and command-specific options
|
127
|
-
|
126
|
+
-follow* a `command`, see the output of `imapcli` (without command or options)
|
128
127
|
for more information.
|
129
128
|
|
130
129
|
Note: The following examples use the command `imapcli`. Depending on how you
|
@@ -187,13 +186,15 @@ of messages in them, this may take a little while.
|
|
187
186
|
|
188
187
|
`imapcli` prints the following statistics about the message sizes in a mailbox:
|
189
188
|
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
189
|
+
- `Count`: Number of individual messages
|
190
|
+
- `Total size`: Total size of all messages in the mailbox
|
191
|
+
- `Min`: Size of the smallest message in the mailbox
|
192
|
+
- `Q1`: First quartile of message sizes in the mailbox
|
193
|
+
- `Median`: Median of all message sizes in the mailbox
|
194
|
+
- `Q3`: Third quartile of message sizes in the mailbox
|
195
|
+
- `Max`: Size of the largest message in the mailbox
|
196
|
+
|
197
|
+
Use the `-H` or `--human` switch to output the message sizes with SI prefixes.
|
197
198
|
|
198
199
|
#### All mailboxes
|
199
200
|
|
@@ -245,13 +246,20 @@ Use the `-r`/`--recurse` flag:
|
|
245
246
|
By default, mailboxes are sorted alphabetically. To sort by a specific statistic,
|
246
247
|
use an `-o`/`--sort` option:
|
247
248
|
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
249
|
+
- `-o count`
|
250
|
+
- `-o total_size`
|
251
|
+
- `-o min_size`
|
252
|
+
- `-o q1_size`
|
253
|
+
- `-o median_size`
|
254
|
+
- `-o q3_size`
|
255
|
+
- `-o max_size`
|
256
|
+
|
257
|
+
This will sort the output from smallest to largest (ascending).
|
258
|
+
|
259
|
+
Use the `--reverse` switch to sort from largest to smallest (descending).
|
260
|
+
|
261
|
+
Note that these are options to the `stats` command and need to come after the `stats`
|
262
|
+
keyword, as shown below.
|
255
263
|
|
256
264
|
Example:
|
257
265
|
|
@@ -268,38 +276,28 @@ following:
|
|
268
276
|
|
269
277
|
### IMAP folder size script
|
270
278
|
|
271
|
-
|
279
|
+
- <https://code.iamcal.com/pl/imap_folders>
|
272
280
|
|
273
281
|
Ad-hoc perl script that computes the sizes of each mailbox. `imapcli` was
|
274
282
|
inspired by this!
|
275
283
|
|
276
284
|
### IMAP synchronization and backup tools
|
277
285
|
|
278
|
-
|
286
|
+
- <https://github.com/OfflineIMAP/imapfw>
|
279
287
|
|
280
288
|
Framework to work with mails
|
281
289
|
|
282
|
-
|
290
|
+
- <https://github.com/polo2ro/imapbox>
|
283
291
|
|
284
292
|
Pull down e-mails from an IMAP server to your local disk
|
285
293
|
|
286
|
-
## State of the project
|
287
|
-
|
288
|
-
I have not been able to work on this project for quite some time. It still
|
289
|
-
serves me well when I occasionally need it. Pull requests are of course welcome.
|
290
|
-
|
291
|
-
I've decided to have one `main` branch, and to get rid of the `master` and
|
292
|
-
`
|
293
|
-
|
294
|
-
This project is [semantically versioned](https://semver.org).
|
295
|
-
|
296
294
|
### To do
|
297
295
|
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
296
|
+
- [x] More human-friendly number formatting (e.g., MiB/GiB as appropriate)
|
297
|
+
- [ ] Output to file
|
298
|
+
- [ ] Deal with server-specific mailbox separator characters (e.g. '.' vs. '/')
|
299
|
+
- [ ] Man page
|
300
|
+
- [ ] More commands?
|
303
301
|
|
304
302
|
## Credits
|
305
303
|
|
@@ -308,9 +306,14 @@ gem by [David Copeland](https://github.com/davetron5000) and makes extensive use
|
|
308
306
|
of [Piotr Murach's](https://github.com/piotrmurach) excellent `TTY` tools. See
|
309
307
|
the `Gemfile` for other work that this tool depends on.
|
310
308
|
|
309
|
+
## Contributors
|
310
|
+
|
311
|
+
- [@bovender](https://github.com/bovender)
|
312
|
+
- [@n-rodriguez](https://github.com/n-rodriguez)
|
313
|
+
|
311
314
|
## License
|
312
315
|
|
313
|
-
© 2017
|
316
|
+
© 2017-2025 Daniel Kraus (@bovender)
|
314
317
|
|
315
318
|
Licensed under the Apache License, Version 2.0 (the "License");
|
316
319
|
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('activesupport', '~> 8.0')
|
26
|
+
s.add_dependency('csv')
|
27
|
+
s.add_dependency('descriptive_statistics', '~> 2.5')
|
28
|
+
s.add_dependency('dotenv', '~> 3.1')
|
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
|