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