PageRenderTime 204ms CodeModel.GetById 175ms app.highlight 26ms RepoModel.GetById 1ms app.codeStats 0ms

/tools/Ruby/lib/ruby/site_ruby/1.8/rubygems/user_interaction.rb

http://github.com/agross/netopenspace
Ruby | 562 lines | 325 code | 137 blank | 100 comment | 36 complexity | 6f142931bb10562743a5084cde3e141e MD5 | raw file
  1#--
  2# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
  3# All rights reserved.
  4# See LICENSE.txt for permissions.
  5#++
  6
  7##
  8# Module that defines the default UserInteraction.  Any class including this
  9# module will have access to the +ui+ method that returns the default UI.
 10
 11module Gem::DefaultUserInteraction
 12
 13  ##
 14  # The default UI is a class variable of the singleton class for this
 15  # module.
 16
 17  @ui = nil
 18
 19  ##
 20  # Return the default UI.
 21
 22  def self.ui
 23    @ui ||= Gem::ConsoleUI.new
 24  end
 25
 26  ##
 27  # Set the default UI.  If the default UI is never explicitly set, a simple
 28  # console based UserInteraction will be used automatically.
 29
 30  def self.ui=(new_ui)
 31    @ui = new_ui
 32  end
 33
 34  ##
 35  # Use +new_ui+ for the duration of +block+.
 36
 37  def self.use_ui(new_ui)
 38    old_ui = @ui
 39    @ui = new_ui
 40    yield
 41  ensure
 42    @ui = old_ui
 43  end
 44
 45  ##
 46  # See DefaultUserInteraction::ui
 47
 48  def ui
 49    Gem::DefaultUserInteraction.ui
 50  end
 51
 52  ##
 53  # See DefaultUserInteraction::ui=
 54
 55  def ui=(new_ui)
 56    Gem::DefaultUserInteraction.ui = new_ui
 57  end
 58
 59  ##
 60  # See DefaultUserInteraction::use_ui
 61
 62  def use_ui(new_ui, &block)
 63    Gem::DefaultUserInteraction.use_ui(new_ui, &block)
 64  end
 65
 66end
 67
 68##
 69# Make the default UI accessible without the "ui." prefix.  Classes
 70# including this module may use the interaction methods on the default UI
 71# directly.  Classes may also reference the ui and ui= methods.
 72#
 73# Example:
 74#
 75#   class X
 76#     include Gem::UserInteraction
 77#
 78#     def get_answer
 79#       n = ask("What is the meaning of life?")
 80#     end
 81#   end
 82
 83module Gem::UserInteraction
 84
 85  include Gem::DefaultUserInteraction
 86
 87  def alert(*args)
 88    ui.alert(*args)
 89  end
 90
 91  def alert_error(*args)
 92    ui.alert_error(*args)
 93  end
 94
 95  def alert_warning(*args)
 96    ui.alert_warning(*args)
 97  end
 98
 99  def ask(*args)
100    ui.ask(*args)
101  end
102
103  def ask_for_password(*args)
104    ui.ask_for_password(*args)
105  end
106
107  def ask_yes_no(*args)
108    ui.ask_yes_no(*args)
109  end
110
111  def choose_from_list(*args)
112    ui.choose_from_list(*args)
113  end
114
115  def say(*args)
116    ui.say(*args)
117  end
118
119  def terminate_interaction(*args)
120    ui.terminate_interaction(*args)
121  end
122end
123
124##
125# Gem::StreamUI implements a simple stream based user interface.
126
127class Gem::StreamUI
128
129  attr_reader :ins, :outs, :errs
130
131  def initialize(in_stream, out_stream, err_stream=STDERR, usetty=true)
132    @ins = in_stream
133    @outs = out_stream
134    @errs = err_stream
135    @usetty = usetty
136  end
137
138  def tty?
139    if RUBY_PLATFORM =~ /mingw|mswin/
140      @usetty
141    else
142      @usetty && @ins.tty?
143    end
144  end
145
146  ##
147  # Choose from a list of options.  +question+ is a prompt displayed above
148  # the list.  +list+ is a list of option strings.  Returns the pair
149  # [option_name, option_index].
150
151  def choose_from_list(question, list)
152    @outs.puts question
153
154    list.each_with_index do |item, index|
155      @outs.puts " #{index+1}. #{item}"
156    end
157
158    @outs.print "> "
159    @outs.flush
160
161    result = @ins.gets
162
163    return nil, nil unless result
164
165    result = result.strip.to_i - 1
166    return list[result], result
167  end
168
169  ##
170  # Ask a question.  Returns a true for yes, false for no.  If not connected
171  # to a tty, raises an exception if default is nil, otherwise returns
172  # default.
173
174  def ask_yes_no(question, default=nil)
175    unless tty? then
176      if default.nil? then
177        raise Gem::OperationNotSupportedError,
178              "Not connected to a tty and no default specified"
179      else
180        return default
181      end
182    end
183
184    default_answer = case default
185                     when nil
186                       'yn'
187                     when true
188                       'Yn'
189                     else
190                       'yN'
191                     end
192
193    result = nil
194
195    while result.nil? do
196      result = case ask "#{question} [#{default_answer}]"
197               when /^y/i then true
198               when /^n/i then false
199               when /^$/  then default
200               else            nil
201               end
202    end
203
204    return result
205  end
206
207  ##
208  # Ask a question.  Returns an answer if connected to a tty, nil otherwise.
209
210  def ask(question)
211    return nil if not tty?
212
213    @outs.print(question + "  ")
214    @outs.flush
215
216    result = @ins.gets
217    result.chomp! if result
218    result
219  end
220
221  if RUBY_VERSION > '1.9.2' then
222    ##
223    # Ask for a password. Does not echo response to terminal.
224
225    def ask_for_password(question)
226      return nil if not tty?
227
228      require 'io/console'
229
230      @outs.print(question + "  ")
231      @outs.flush
232
233      password = @ins.noecho {@ins.gets}
234      password.chomp! if password
235      password
236    end
237  else
238    ##
239    # Ask for a password. Does not echo response to terminal.
240
241    def ask_for_password(question)
242      return nil if not tty?
243
244      @outs.print(question + "  ")
245      @outs.flush
246
247      Gem.win_platform? ? ask_for_password_on_windows : ask_for_password_on_unix
248    end
249
250    ##
251    # Asks for a password that works on windows. Ripped from the Heroku gem.
252
253    def ask_for_password_on_windows
254      return nil if not tty?
255
256      require "Win32API"
257      char = nil
258      password = ''
259
260      while char = Win32API.new("crtdll", "_getch", [ ], "L").Call do
261        break if char == 10 || char == 13 # received carriage return or newline
262        if char == 127 || char == 8 # backspace and delete
263          password.slice!(-1, 1)
264        else
265          password << char.chr
266        end
267      end
268
269      puts
270      password
271    end
272
273    ##
274    # Asks for a password that works on unix
275
276    def ask_for_password_on_unix
277      return nil if not tty?
278
279      system "stty -echo"
280      password = @ins.gets
281      password.chomp! if password
282      system "stty echo"
283      password
284    end
285  end
286
287  ##
288  # Display a statement.
289
290  def say(statement="")
291    @outs.puts statement
292  end
293
294  ##
295  # Display an informational alert.  Will ask +question+ if it is not nil.
296
297  def alert(statement, question=nil)
298    @outs.puts "INFO:  #{statement}"
299    ask(question) if question
300  end
301
302  ##
303  # Display a warning in a location expected to get error messages.  Will
304  # ask +question+ if it is not nil.
305
306  def alert_warning(statement, question=nil)
307    @errs.puts "WARNING:  #{statement}"
308    ask(question) if question
309  end
310
311  ##
312  # Display an error message in a location expected to get error messages.
313  # Will ask +question+ if it is not nil.
314
315  def alert_error(statement, question=nil)
316    @errs.puts "ERROR:  #{statement}"
317    ask(question) if question
318  end
319
320  ##
321  # Display a debug message on the same location as error messages.
322
323  def debug(statement)
324    @errs.puts statement
325  end
326
327  ##
328  # Terminate the application with exit code +status+, running any exit
329  # handlers that might have been defined.
330
331  def terminate_interaction(status = 0)
332    raise Gem::SystemExitException, status
333  end
334
335  ##
336  # Return a progress reporter object chosen from the current verbosity.
337
338  def progress_reporter(*args)
339    if self.kind_of?(Gem::SilentUI)
340      return SilentProgressReporter.new(@outs, *args)
341    end
342
343    case Gem.configuration.verbose
344    when nil, false
345      SilentProgressReporter.new(@outs, *args)
346    when true
347      SimpleProgressReporter.new(@outs, *args)
348    else
349      VerboseProgressReporter.new(@outs, *args)
350    end
351  end
352
353  ##
354  # An absolutely silent progress reporter.
355
356  class SilentProgressReporter
357    attr_reader :count
358
359    def initialize(out_stream, size, initial_message, terminal_message = nil)
360    end
361
362    def updated(message)
363    end
364
365    def done
366    end
367  end
368
369  ##
370  # A basic dotted progress reporter.
371
372  class SimpleProgressReporter
373
374    include Gem::DefaultUserInteraction
375
376    attr_reader :count
377
378    def initialize(out_stream, size, initial_message,
379                   terminal_message = "complete")
380      @out = out_stream
381      @total = size
382      @count = 0
383      @terminal_message = terminal_message
384
385      @out.puts initial_message
386    end
387
388    ##
389    # Prints out a dot and ignores +message+.
390
391    def updated(message)
392      @count += 1
393      @out.print "."
394      @out.flush
395    end
396
397    ##
398    # Prints out the terminal message.
399
400    def done
401      @out.puts "\n#{@terminal_message}"
402    end
403
404  end
405
406  ##
407  # A progress reporter that prints out messages about the current progress.
408
409  class VerboseProgressReporter
410
411    include Gem::DefaultUserInteraction
412
413    attr_reader :count
414
415    def initialize(out_stream, size, initial_message,
416                   terminal_message = 'complete')
417      @out = out_stream
418      @total = size
419      @count = 0
420      @terminal_message = terminal_message
421
422      @out.puts initial_message
423    end
424
425    ##
426    # Prints out the position relative to the total and the +message+.
427
428    def updated(message)
429      @count += 1
430      @out.puts "#{@count}/#{@total}: #{message}"
431    end
432
433    ##
434    # Prints out the terminal message.
435
436    def done
437      @out.puts @terminal_message
438    end
439  end
440
441  ##
442  # Return a download reporter object chosen from the current verbosity
443
444  def download_reporter(*args)
445    if self.kind_of?(Gem::SilentUI)
446      return SilentDownloadReporter.new(@outs, *args)
447    end
448
449    case Gem.configuration.verbose
450    when nil, false
451      SilentDownloadReporter.new(@outs, *args)
452    else
453      VerboseDownloadReporter.new(@outs, *args)
454    end
455  end
456
457  ##
458  # An absolutely silent download reporter.
459
460  class SilentDownloadReporter
461    def initialize(out_stream, *args)
462    end
463
464    def fetch(filename, filesize)
465    end
466
467    def update(current)
468    end
469
470    def done
471    end
472  end
473
474  ##
475  # A progress reporter that prints out messages about the current progress.
476
477  class VerboseDownloadReporter
478    attr_reader :file_name, :total_bytes, :progress
479
480    def initialize(out_stream, *args)
481      @out = out_stream
482      @progress = 0
483    end
484
485    def fetch(file_name, total_bytes)
486      @file_name = file_name
487      @total_bytes = total_bytes.to_i
488      @units = @total_bytes.zero? ? 'B' : '%'
489
490      update_display(false)
491    end
492
493    def update(bytes)
494      new_progress = if @units == 'B' then
495                       bytes
496                     else
497                       ((bytes.to_f * 100) / total_bytes.to_f).ceil
498                     end
499
500      return if new_progress == @progress
501
502      @progress = new_progress
503      update_display
504    end
505
506    def done
507      @progress = 100 if @units == '%'
508      update_display(true, true)
509    end
510
511    private
512
513    def update_display(show_progress = true, new_line = false)
514      return unless @out.tty?
515
516      if show_progress then
517        @out.print "\rFetching: %s (%3d%s)" % [@file_name, @progress, @units]
518      else
519        @out.print "Fetching: %s" % @file_name
520      end
521      @out.puts if new_line
522    end
523  end
524end
525
526##
527# Subclass of StreamUI that instantiates the user interaction using STDIN,
528# STDOUT, and STDERR.
529
530class Gem::ConsoleUI < Gem::StreamUI
531  def initialize
532    super STDIN, STDOUT, STDERR, true
533  end
534end
535
536##
537# SilentUI is a UI choice that is absolutely silent.
538
539class Gem::SilentUI < Gem::StreamUI
540  def initialize
541    reader, writer = nil, nil
542
543    begin
544      reader = File.open('/dev/null', 'r')
545      writer = File.open('/dev/null', 'w')
546    rescue Errno::ENOENT
547      reader = File.open('nul', 'r')
548      writer = File.open('nul', 'w')
549    end
550
551    super reader, writer, writer, false
552  end
553
554  def download_reporter(*args)
555    SilentDownloadReporter.new(@outs, *args)
556  end
557
558  def progress_reporter(*args)
559    SilentProgressReporter.new(@outs, *args)
560  end
561end
562