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.
data/README.md CHANGED
@@ -9,15 +9,14 @@ IMAP mailbox sizes.
9
9
 
10
10
  ## Table of contents
11
11
 
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)
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 bin/imapcli
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
- *follow* a `command`, see the output of `imapcli` (without command or options)
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
- * `Count`: Number of individual messages
191
- * `Total size`: Total size of all messages in the mailbox (in kiB)
192
- * `Min`: Size of the smallest message in the mailbox (in kiB)
193
- * `Q1`: First quartile of message sizes in the mailbox (in kiB)
194
- * `Median`: Median of all message sizes in the mailbox (in kiB)
195
- * `Q3`: First quartile of message sizes in the mailbox (in kiB)
196
- * `Max`: Size of the largest message in the mailbox (in kiB)
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
- * `-o count`
249
- * `-o total_size`
250
- * `-o min_size`
251
- * `-o q1`
252
- * `-o median_size`
253
- * `-o q3`
254
- * `-o max_size`
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
- * <https://code.iamcal.com/pl/imap_folders>
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
- * <https://github.com/OfflineIMAP/imapfw>
286
+ - <https://github.com/OfflineIMAP/imapfw>
279
287
 
280
288
  Framework to work with mails
281
289
 
282
- * <https://github.com/polo2ro/imapbox>
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
- * More human-friendly number formatting (e.g., MiB/GiB as appropriate)
299
- * Output to file
300
- * Deal with server-specific mailbox separator characters (e.g. '.' vs. '/')
301
- * Man page
302
- * More commands?
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
- &copy; 2017, 2022 Daniel Kraus (bovender)
316
+ &copy; 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 = "README.md"
9
- rd.rdoc_files.include("README.md","lib/**/*.rb","bin/**/*")
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
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require_relative '../lib/imapcli'
5
+
6
+ Dotenv.load
7
+
8
+ exit Imapcli::Cli.run(ARGV)
data/imapcli.gemspec CHANGED
@@ -1,27 +1,35 @@
1
- # Ensure we require the local version and not one we might have installed already
2
- require File.join([File.dirname(__FILE__),'lib','imapcli','version.rb'])
3
- spec = Gem::Specification.new do |s|
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
- s.require_paths << 'lib'
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
- s.bindir = 'bin'
17
- s.executables << 'imapcli'
18
- s.add_development_dependency('rake', '~> 12.3.3')
19
- s.add_development_dependency('rdoc', '~> 6.3')
20
- s.add_runtime_dependency('descriptive_statistics', '~> 2.5')
21
- s.add_runtime_dependency('dotenv', '~> 2.2')
22
- s.add_runtime_dependency('filesize', '~> 0.1')
23
- s.add_runtime_dependency('gli','~> 2.17')
24
- s.add_runtime_dependency('tty-progressbar', '~> 0.13')
25
- s.add_runtime_dependency('tty-prompt', '~> 0.13')
26
- s.add_runtime_dependency('tty-table', '~> 0.9')
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
@@ -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