PageRenderTime 31ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/thor/shell/basic.rb

https://github.com/tf/thor
Ruby | 306 lines | 191 code | 47 blank | 68 comment | 26 complexity | d4d5a25714cd10502c56c5d8e5759dd8 MD5 | raw file
  1. require 'tempfile'
  2. class Thor
  3. module Shell
  4. class Basic
  5. attr_accessor :base, :padding
  6. # Initialize base, mute and padding to nil.
  7. #
  8. def initialize #:nodoc:
  9. @base, @mute, @padding = nil, false, 0
  10. end
  11. # Mute everything that's inside given block
  12. #
  13. def mute
  14. @mute = true
  15. yield
  16. ensure
  17. @mute = false
  18. end
  19. # Check if base is muted
  20. #
  21. def mute?
  22. @mute
  23. end
  24. # Sets the output padding, not allowing less than zero values.
  25. #
  26. def padding=(value)
  27. @padding = [0, value].max
  28. end
  29. # Ask something to the user and receives a response.
  30. #
  31. # ==== Example
  32. # ask("What is your name?")
  33. #
  34. def ask(statement, color=nil)
  35. say("#{statement} ", color)
  36. stdin.gets.strip
  37. end
  38. # Say (print) something to the user. If the sentence ends with a whitespace
  39. # or tab character, a new line is not appended (print + flush). Otherwise
  40. # are passed straight to puts (behavior got from Highline).
  41. #
  42. # ==== Example
  43. # say("I know you knew that.")
  44. #
  45. def say(message="", color=nil, force_new_line=(message.to_s !~ /( |\t)$/))
  46. message = message.to_s
  47. message = set_color(message, color) if color
  48. spaces = " " * padding
  49. if force_new_line
  50. stdout.puts(spaces + message)
  51. else
  52. stdout.print(spaces + message)
  53. end
  54. stdout.flush
  55. end
  56. # Say a status with the given color and appends the message. Since this
  57. # method is used frequently by actions, it allows nil or false to be given
  58. # in log_status, avoiding the message from being shown. If a Symbol is
  59. # given in log_status, it's used as the color.
  60. #
  61. def say_status(status, message, log_status=true)
  62. return if quiet? || log_status == false
  63. spaces = " " * (padding + 1)
  64. color = log_status.is_a?(Symbol) ? log_status : :green
  65. status = status.to_s.rjust(12)
  66. status = set_color status, color, true if color
  67. stdout.puts "#{status}#{spaces}#{message}"
  68. stdout.flush
  69. end
  70. # Make a question the to user and returns true if the user replies "y" or
  71. # "yes".
  72. #
  73. def yes?(statement, color=nil)
  74. !!(ask(statement, color) =~ is?(:yes))
  75. end
  76. # Make a question the to user and returns true if the user replies "n" or
  77. # "no".
  78. #
  79. def no?(statement, color=nil)
  80. !yes?(statement, color)
  81. end
  82. # Prints a table.
  83. #
  84. # ==== Parameters
  85. # Array[Array[String, String, ...]]
  86. #
  87. # ==== Options
  88. # ident<Integer>:: Indent the first column by ident value.
  89. # colwidth<Integer>:: Force the first column to colwidth spaces wide.
  90. #
  91. def print_table(table, options={})
  92. return if table.empty?
  93. formats, ident, colwidth = [], options[:ident].to_i, options[:colwidth]
  94. options[:truncate] = terminal_width if options[:truncate] == true
  95. formats << "%-#{colwidth + 2}s" if colwidth
  96. start = colwidth ? 1 : 0
  97. start.upto(table.first.length - 2) do |i|
  98. maxima ||= table.max{|a,b| a[i].size <=> b[i].size }[i].size
  99. formats << "%-#{maxima + 2}s"
  100. end
  101. formats[0] = formats[0].insert(0, " " * ident)
  102. formats << "%s"
  103. table.each do |row|
  104. sentence = ""
  105. row.each_with_index do |column, i|
  106. sentence << formats[i] % column.to_s
  107. end
  108. sentence = truncate(sentence, options[:truncate]) if options[:truncate]
  109. stdout.puts sentence
  110. end
  111. end
  112. # Prints a long string, word-wrapping the text to the current width of the
  113. # terminal display. Ideal for printing heredocs.
  114. #
  115. # ==== Parameters
  116. # String
  117. #
  118. # ==== Options
  119. # ident<Integer>:: Indent each line of the printed paragraph by ident value.
  120. #
  121. def print_wrapped(message, options={})
  122. ident = options[:ident] || 0
  123. width = terminal_width - ident
  124. paras = message.split("\n\n")
  125. paras.map! do |unwrapped|
  126. unwrapped.strip.gsub(/\n/, " ").squeeze(" ").
  127. gsub(/.{1,#{width}}(?:\s|\Z)/){($& + 5.chr).
  128. gsub(/\n\005/,"\n").gsub(/\005/,"\n")}
  129. end
  130. paras.each do |para|
  131. para.split("\n").each do |line|
  132. stdout.puts line.insert(0, " " * ident)
  133. end
  134. stdout.puts unless para == paras.last
  135. end
  136. end
  137. # Deals with file collision and returns true if the file should be
  138. # overwriten and false otherwise. If a block is given, it uses the block
  139. # response as the content for the diff.
  140. #
  141. # ==== Parameters
  142. # destination<String>:: the destination file to solve conflicts
  143. # block<Proc>:: an optional block that returns the value to be used in diff
  144. #
  145. def file_collision(destination)
  146. return true if @always_force
  147. return false if @always_skip
  148. options = block_given? ? "[Ynakqdh]" : "[Ynakqh]"
  149. while true
  150. answer = ask %[Overwrite #{destination}? (enter "h" for help) #{options}]
  151. case answer
  152. when is?(:yes), is?(:force), ""
  153. return true
  154. when is?(:no), is?(:skip)
  155. return false
  156. when is?(:always)
  157. return @always_force = true
  158. when is?(:keep)
  159. return @always_skip = true
  160. when is?(:quit)
  161. say 'Aborting...'
  162. raise SystemExit
  163. when is?(:diff)
  164. show_diff(destination, yield) if block_given?
  165. say 'Retrying...'
  166. else
  167. say file_collision_help
  168. end
  169. end
  170. end
  171. # Called if something goes wrong during the execution. This is used by Thor
  172. # internally and should not be used inside your scripts. If something went
  173. # wrong, you can always raise an exception. If you raise a Thor::Error, it
  174. # will be rescued and wrapped in the method below.
  175. #
  176. def error(statement)
  177. stderr.puts statement
  178. end
  179. # Apply color to the given string with optional bold. Disabled in the
  180. # Thor::Shell::Basic class.
  181. #
  182. def set_color(string, color, bold=false) #:nodoc:
  183. string
  184. end
  185. protected
  186. def stdout
  187. $stdout
  188. end
  189. def stdin
  190. $stdin
  191. end
  192. def stderr
  193. $stderr
  194. end
  195. def is?(value) #:nodoc:
  196. value = value.to_s
  197. if value.size == 1
  198. /\A#{value}\z/i
  199. else
  200. /\A(#{value}|#{value[0,1]})\z/i
  201. end
  202. end
  203. def file_collision_help #:nodoc:
  204. <<HELP
  205. Y - yes, overwrite
  206. n - no, do not overwrite
  207. a - all, overwrite this and all others
  208. k - keep, do not overwrite any files
  209. q - quit, abort
  210. d - diff, show the differences between the old and the new
  211. h - help, show this help
  212. HELP
  213. end
  214. def show_diff(destination, content) #:nodoc:
  215. diff_cmd = ENV['THOR_DIFF'] || ENV['RAILS_DIFF'] || 'diff -u'
  216. Tempfile.open(File.basename(destination), File.dirname(destination)) do |temp|
  217. temp.write content
  218. temp.rewind
  219. system %(#{diff_cmd} "#{destination}" "#{temp.path}")
  220. end
  221. end
  222. def quiet? #:nodoc:
  223. mute? || (base && base.options[:quiet])
  224. end
  225. # This code was copied from Rake, available under MIT-LICENSE
  226. # Copyright (c) 2003, 2004 Jim Weirich
  227. def terminal_width
  228. if ENV['THOR_COLUMNS']
  229. result = ENV['THOR_COLUMNS'].to_i
  230. else
  231. result = unix? ? dynamic_width : 80
  232. end
  233. (result < 10) ? 80 : result
  234. rescue
  235. 80
  236. end
  237. # Calculate the dynamic width of the terminal
  238. def dynamic_width
  239. @dynamic_width ||= (dynamic_width_stty.nonzero? || dynamic_width_tput)
  240. end
  241. def dynamic_width_stty
  242. %x{stty size 2>/dev/null}.split[1].to_i
  243. end
  244. def dynamic_width_tput
  245. %x{tput cols 2>/dev/null}.to_i
  246. end
  247. def unix?
  248. RUBY_PLATFORM =~ /(aix|darwin|linux|(net|free|open)bsd|cygwin|solaris|irix|hpux)/i
  249. end
  250. def truncate(string, width)
  251. if string.length <= width
  252. string
  253. else
  254. ( string[0, width-3] || "" ) + "..."
  255. end
  256. end
  257. end
  258. end
  259. end