summaryrefslogtreecommitdiff
path: root/lib/rubygems/user_interaction.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/rubygems/user_interaction.rb')
-rw-r--r--lib/rubygems/user_interaction.rb243
1 files changed, 95 insertions, 148 deletions
diff --git a/lib/rubygems/user_interaction.rb b/lib/rubygems/user_interaction.rb
index 390d0f2aea..9fe3e755c4 100644
--- a/lib/rubygems/user_interaction.rb
+++ b/lib/rubygems/user_interaction.rb
@@ -1,22 +1,19 @@
# frozen_string_literal: true
+
#--
# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
# All rights reserved.
# See LICENSE.txt for permissions.
#++
-require 'rubygems/util'
-
-begin
- require 'io/console'
-rescue LoadError
-end
+require_relative "text"
##
# Module that defines the default UserInteraction. Any class including this
# module will have access to the +ui+ method that returns the default UI.
module Gem::DefaultUserInteraction
+ include Gem::Text
##
# The default UI is a class variable of the singleton class for this
@@ -70,7 +67,6 @@ module Gem::DefaultUserInteraction
def use_ui(new_ui, &block)
Gem::DefaultUserInteraction.use_ui(new_ui, &block)
end
-
end
##
@@ -93,13 +89,12 @@ end
# end
module Gem::UserInteraction
-
include Gem::DefaultUserInteraction
##
# Displays an alert +statement+. Asks a +question+ if given.
- def alert statement, question = nil
+ def alert(statement, question = nil)
ui.alert statement, question
end
@@ -107,7 +102,7 @@ module Gem::UserInteraction
# Displays an error +statement+ to the error output location. Asks a
# +question+ if given.
- def alert_error statement, question = nil
+ def alert_error(statement, question = nil)
ui.alert_error statement, question
end
@@ -115,49 +110,49 @@ module Gem::UserInteraction
# Displays a warning +statement+ to the warning output location. Asks a
# +question+ if given.
- def alert_warning statement, question = nil
+ def alert_warning(statement, question = nil)
ui.alert_warning statement, question
end
##
# Asks a +question+ and returns the answer.
- def ask question
+ def ask(question)
ui.ask question
end
##
# Asks for a password with a +prompt+
- def ask_for_password prompt
+ def ask_for_password(prompt)
ui.ask_for_password prompt
end
##
# Asks a yes or no +question+. Returns true for yes, false for no.
- def ask_yes_no question, default = nil
+ def ask_yes_no(question, default = nil)
ui.ask_yes_no question, default
end
##
# Asks the user to answer +question+ with an answer from the given +list+.
- def choose_from_list question, list
+ def choose_from_list(question, list)
ui.choose_from_list question, list
end
##
# Displays the given +statement+ on the standard output (or equivalent).
- def say statement = ''
+ def say(statement = "")
ui.say statement
end
##
# Terminates the RubyGems process with the given +exit_code+
- def terminate_interaction exit_code = 0
+ def terminate_interaction(exit_code = 0)
ui.terminate_interaction exit_code
end
@@ -165,8 +160,8 @@ module Gem::UserInteraction
# Calls +say+ with +msg+ or the results of the block if really_verbose
# is true.
- def verbose msg = nil
- say(msg || yield) if Gem.configuration.really_verbose
+ def verbose(msg = nil)
+ say(clean_text(msg || yield)) if Gem.configuration.really_verbose
end
end
@@ -174,7 +169,6 @@ end
# Gem::StreamUI implements a simple stream based user interface.
class Gem::StreamUI
-
##
# The input stream
@@ -196,7 +190,7 @@ class Gem::StreamUI
# then special operations (like asking for passwords) will use the TTY
# commands to disable character echo.
- def initialize(in_stream, out_stream, err_stream=STDERR, usetty=true)
+ def initialize(in_stream, out_stream, err_stream = $stderr, usetty = true)
@ins = in_stream
@outs = out_stream
@errs = err_stream
@@ -207,18 +201,14 @@ class Gem::StreamUI
# Returns true if TTY methods should be used on this StreamUI.
def tty?
- if RUBY_VERSION < '1.9.3' and RUBY_PLATFORM =~ /mingw|mswin/ then
- @usetty
- else
- @usetty && @ins.tty?
- end
+ @usetty && @ins.tty?
end
##
# Prints a formatted backtrace to the errors stream if backtraces are
# enabled.
- def backtrace exception
+ def backtrace(exception)
return unless Gem.configuration.backtrace
@errs.puts "\t#{exception.backtrace.join "\n\t"}"
@@ -233,7 +223,7 @@ class Gem::StreamUI
@outs.puts question
list.each_with_index do |item, index|
- @outs.puts " #{index+1}. #{item}"
+ @outs.puts " #{index + 1}. #{item}"
end
@outs.print "> "
@@ -244,7 +234,8 @@ class Gem::StreamUI
return nil, nil unless result
result = result.strip.to_i - 1
- return list[result], result
+ return nil, nil unless (0...list.size) === result
+ [list[result], result]
end
##
@@ -252,9 +243,9 @@ class Gem::StreamUI
# to a tty, raises an exception if default is nil, otherwise returns
# default.
- def ask_yes_no(question, default=nil)
- unless tty? then
- if default.nil? then
+ def ask_yes_no(question, default = nil)
+ unless tty?
+ if default.nil?
raise Gem::OperationNotSupportedError,
"Not connected to a tty and no default specified"
else
@@ -264,12 +255,12 @@ class Gem::StreamUI
default_answer = case default
when nil
- 'yn'
+ "yn"
when true
- 'Yn'
+ "Yn"
else
- 'yN'
- end
+ "yN"
+ end
result = nil
@@ -278,24 +269,23 @@ class Gem::StreamUI
when /^y/i then true
when /^n/i then false
when /^$/ then default
- else nil
- end
+ end
end
- return result
+ result
end
##
# Ask a question. Returns an answer if connected to a tty, nil otherwise.
def ask(question)
- return nil if not tty?
+ return nil unless tty?
@outs.print(question + " ")
@outs.flush
result = @ins.gets
- result.chomp! if result
+ result&.chomp!
result
end
@@ -303,58 +293,43 @@ class Gem::StreamUI
# Ask for a password. Does not echo response to terminal.
def ask_for_password(question)
- return nil if not tty?
+ return nil unless tty?
@outs.print(question, " ")
@outs.flush
password = _gets_noecho
@outs.puts
- password.chomp! if password
+ password&.chomp!
password
end
- if IO.method_defined?(:noecho) then
- def _gets_noecho
- @ins.noecho {@ins.gets}
- end
- elsif Gem.win_platform?
- def _gets_noecho
- require "Win32API"
- password = ''
-
- while char = Win32API.new("crtdll", "_getch", [ ], "L").Call do
- break if char == 10 || char == 13 # received carriage return or newline
- if char == 127 || char == 8 # backspace and delete
- password.slice!(-1, 1)
- else
- password << char.chr
- end
- end
- password
- end
- else
- def _gets_noecho
- system "stty -echo"
+ def require_io_console
+ @require_io_console ||= begin
begin
- @ins.gets
- ensure
- system "stty echo"
+ require "io/console"
+ rescue LoadError
end
+ true
end
end
+ def _gets_noecho
+ require_io_console
+ @ins.noecho { @ins.gets }
+ end
+
##
# Display a statement.
- def say(statement="")
+ def say(statement = "")
@outs.puts statement
end
##
# Display an informational alert. Will ask +question+ if it is not nil.
- def alert(statement, question=nil)
+ def alert(statement, question = nil)
@outs.puts "INFO: #{statement}"
ask(question) if question
end
@@ -362,7 +337,7 @@ class Gem::StreamUI
##
# Display a warning on stderr. Will ask +question+ if it is not nil.
- def alert_warning(statement, question=nil)
+ def alert_warning(statement, question = nil)
@errs.puts "WARNING: #{statement}"
ask(question) if question
end
@@ -371,19 +346,12 @@ class Gem::StreamUI
# Display an error message in a location expected to get error messages.
# Will ask +question+ if it is not nil.
- def alert_error(statement, question=nil)
+ def alert_error(statement, question = nil)
@errs.puts "ERROR: #{statement}"
ask(question) if question
end
##
- # Display a debug message on the same location as error messages.
-
- def debug(statement)
- @errs.puts statement
- end
-
- ##
# Terminate the application with exit code +status+, running any exit
# handlers that might have been defined.
@@ -413,7 +381,6 @@ class Gem::StreamUI
# An absolutely silent progress reporter.
class SilentProgressReporter
-
##
# The count of items is never updated for the silent progress reporter.
@@ -444,7 +411,6 @@ class Gem::StreamUI
# A basic dotted progress reporter.
class SimpleProgressReporter
-
include Gem::DefaultUserInteraction
##
@@ -457,8 +423,7 @@ class Gem::StreamUI
# +size+ items. Shows the given +initial_message+ when progress starts
# and the +terminal_message+ when it is complete.
- def initialize(out_stream, size, initial_message,
- terminal_message = "complete")
+ def initialize(out_stream, size, initial_message, terminal_message = "complete")
@out = out_stream
@total = size
@count = 0
@@ -482,14 +447,12 @@ class Gem::StreamUI
def done
@out.puts "\n#{@terminal_message}"
end
-
end
##
# A progress reporter that prints out messages about the current progress.
class VerboseProgressReporter
-
include Gem::DefaultUserInteraction
##
@@ -502,8 +465,7 @@ class Gem::StreamUI
# +size+ items. Shows the given +initial_message+ when progress starts
# and the +terminal_message+ when it is complete.
- def initialize(out_stream, size, initial_message,
- terminal_message = 'complete')
+ def initialize(out_stream, size, initial_message, terminal_message = "complete")
@out = out_stream
@total = size
@count = 0
@@ -532,11 +494,10 @@ class Gem::StreamUI
# Return a download reporter object chosen from the current verbosity
def download_reporter(*args)
- case Gem.configuration.verbose
- when nil, false
+ if [nil, false].include?(Gem.configuration.verbose) || !@outs.tty?
SilentDownloadReporter.new(@outs, *args)
else
- VerboseDownloadReporter.new(@outs, *args)
+ ThreadedDownloadReporter.new(@outs, *args)
end
end
@@ -544,7 +505,6 @@ class Gem::StreamUI
# An absolutely silent download reporter.
class SilentDownloadReporter
-
##
# The silent download reporter ignores all arguments
@@ -573,9 +533,10 @@ class Gem::StreamUI
end
##
- # A progress reporter that prints out messages about the current progress.
+ # A progress reporter that behaves nicely with threaded downloading.
- class VerboseDownloadReporter
+ class ThreadedDownloadReporter
+ MUTEX = Thread::Mutex.new
##
# The current file name being displayed
@@ -583,87 +544,60 @@ class Gem::StreamUI
attr_reader :file_name
##
- # The total bytes in the file
-
- attr_reader :total_bytes
-
- ##
- # The current progress (0 to 100)
-
- attr_reader :progress
-
- ##
- # Creates a new verbose download reporter that will display on
+ # Creates a new threaded download reporter that will display on
# +out_stream+. The other arguments are ignored.
def initialize(out_stream, *args)
+ @file_name = nil
@out = out_stream
- @progress = 0
end
##
- # Tells the download reporter that the +file_name+ is being fetched and
- # contains +total_bytes+.
+ # Tells the download reporter that the +file_name+ is being fetched.
+ # The other arguments are ignored.
- def fetch(file_name, total_bytes)
- @file_name = file_name
- @total_bytes = total_bytes.to_i
- @units = @total_bytes.zero? ? 'B' : '%'
-
- update_display(false)
+ def fetch(file_name, *args)
+ if @file_name.nil?
+ @file_name = file_name
+ locked_puts "Fetching #{@file_name}"
+ end
end
##
- # Updates the verbose download reporter for the given number of +bytes+.
+ # Updates the threaded download reporter for the given number of +bytes+.
def update(bytes)
- new_progress = if @units == 'B' then
- bytes
- else
- ((bytes.to_f * 100) / total_bytes.to_f).ceil
- end
-
- return if new_progress == @progress
-
- @progress = new_progress
- update_display
+ # Do nothing.
end
##
# Indicates the download is complete.
def done
- @progress = 100 if @units == '%'
- update_display(true, true)
+ # Do nothing.
end
private
- def update_display(show_progress = true, new_line = false) # :nodoc:
- return unless @out.tty?
-
- if show_progress then
- @out.print "\rFetching: %s (%3d%s)" % [@file_name, @progress, @units]
- else
- @out.print "Fetching: %s" % @file_name
+ def locked_puts(message)
+ MUTEX.synchronize do
+ @out.puts message
end
- @out.puts if new_line
end
end
end
##
-# Subclass of StreamUI that instantiates the user interaction using STDIN,
-# STDOUT, and STDERR.
+# Subclass of StreamUI that instantiates the user interaction using $stdin,
+# $stdout, and $stderr.
class Gem::ConsoleUI < Gem::StreamUI
-
##
# The Console UI has no arguments as it defaults to reading input from
# stdin, output to stdout and warnings or errors to stderr.
def initialize
- super STDIN, STDOUT, STDERR, true
+ super $stdin, $stdout, $stderr, true
end
end
@@ -671,23 +605,15 @@ end
# SilentUI is a UI choice that is absolutely silent.
class Gem::SilentUI < Gem::StreamUI
-
##
# The SilentUI has no arguments as it does not use any stream.
def initialize
- reader, writer = nil, nil
-
- reader = File.open(Gem::Util::NULL_DEVICE, 'r')
- writer = File.open(Gem::Util::NULL_DEVICE, 'w')
-
- super reader, writer, writer, false
+ io = NullIO.new
+ super io, io, io, false
end
def close
- super
- @ins.close
- @outs.close
end
def download_reporter(*args) # :nodoc:
@@ -697,4 +623,25 @@ class Gem::SilentUI < Gem::StreamUI
def progress_reporter(*args) # :nodoc:
SilentProgressReporter.new(@outs, *args)
end
+
+ ##
+ # An absolutely silent IO.
+
+ class NullIO
+ def puts(*args)
+ end
+
+ def print(*args)
+ end
+
+ def flush
+ end
+
+ def gets(*args)
+ end
+
+ def tty?
+ false
+ end
+ end
end