qthor 0.19.1.5

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.
@@ -0,0 +1,81 @@
1
+ require "rbconfig"
2
+
3
+ class Thor
4
+ module Base
5
+ class << self
6
+ attr_writer :shell
7
+
8
+ # Returns the shell used in all Thor classes. If you are in a Unix platform
9
+ # it will use a colored log, otherwise it will use a basic one without color.
10
+ #
11
+ def shell
12
+ @shell ||= if ENV["THOR_SHELL"] && ENV["THOR_SHELL"].size > 0
13
+ Thor::Shell.const_get(ENV["THOR_SHELL"])
14
+ elsif RbConfig::CONFIG["host_os"] =~ /mswin|mingw/ && !ENV["ANSICON"]
15
+ Thor::Shell::Basic
16
+ else
17
+ Thor::Shell::Color
18
+ end
19
+ end
20
+ end
21
+ end
22
+
23
+ module Shell
24
+ SHELL_DELEGATED_METHODS = [:ask, :error, :set_color, :yes?, :no?, :say, :say_status, :print_in_columns, :print_table, :print_wrapped, :file_collision, :terminal_width]
25
+ attr_writer :shell
26
+
27
+ autoload :Basic, "thor/shell/basic"
28
+ autoload :Color, "thor/shell/color"
29
+ autoload :HTML, "thor/shell/html"
30
+
31
+ # Add shell to initialize config values.
32
+ #
33
+ # ==== Configuration
34
+ # shell<Object>:: An instance of the shell to be used.
35
+ #
36
+ # ==== Examples
37
+ #
38
+ # class MyScript < Thor
39
+ # argument :first, :type => :numeric
40
+ # end
41
+ #
42
+ # MyScript.new [1.0], { :foo => :bar }, :shell => Thor::Shell::Basic.new
43
+ #
44
+ def initialize(args = [], options = {}, config = {})
45
+ super
46
+ self.shell = config[:shell]
47
+ shell.base ||= self if shell.respond_to?(:base)
48
+ end
49
+
50
+ # Holds the shell for the given Thor instance. If no shell is given,
51
+ # it gets a default shell from Thor::Base.shell.
52
+ def shell
53
+ @shell ||= Thor::Base.shell.new
54
+ end
55
+
56
+ # Common methods that are delegated to the shell.
57
+ SHELL_DELEGATED_METHODS.each do |method|
58
+ module_eval <<-METHOD, __FILE__, __LINE__
59
+ def #{method}(*args,&block)
60
+ shell.#{method}(*args,&block)
61
+ end
62
+ METHOD
63
+ end
64
+
65
+ # Yields the given block with padding.
66
+ def with_padding
67
+ shell.padding += 1
68
+ yield
69
+ ensure
70
+ shell.padding -= 1
71
+ end
72
+
73
+ protected
74
+
75
+ # Allow shell to be shared between invocations.
76
+ #
77
+ def _shared_configuration #:nodoc:
78
+ super.merge!(:shell => shell)
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,430 @@
1
+ require "tempfile"
2
+ require "io/console" if RUBY_VERSION > "1.9.2"
3
+
4
+ class Thor
5
+ module Shell
6
+ class Basic # rubocop:disable ClassLength
7
+ attr_accessor :base
8
+ attr_reader :padding
9
+
10
+ # Initialize base, mute and padding to nil.
11
+ #
12
+ def initialize #:nodoc:
13
+ @base, @mute, @padding, @always_force = nil, false, 0, false
14
+ end
15
+
16
+ # Mute everything that's inside given block
17
+ #
18
+ def mute
19
+ @mute = true
20
+ yield
21
+ ensure
22
+ @mute = false
23
+ end
24
+
25
+ # Check if base is muted
26
+ #
27
+ def mute? # rubocop:disable TrivialAccessors
28
+ @mute
29
+ end
30
+
31
+ # Sets the output padding, not allowing less than zero values.
32
+ #
33
+ def padding=(value)
34
+ @padding = [0, value].max
35
+ end
36
+
37
+ # Sets the output padding while executing a block and resets it.
38
+ #
39
+ def indent(count = 1, &block)
40
+ orig_padding = padding
41
+ self.padding = padding + count
42
+ yield
43
+ self.padding = orig_padding
44
+ end
45
+
46
+ # Asks something to the user and receives a response.
47
+ #
48
+ # If asked to limit the correct responses, you can pass in an
49
+ # array of acceptable answers. If one of those is not supplied,
50
+ # they will be shown a message stating that one of those answers
51
+ # must be given and re-asked the question.
52
+ #
53
+ # If asking for sensitive information, the :echo option can be set
54
+ # to false to mask user input from $stdin.
55
+ #
56
+ # If the required input is a path, then set the path option to
57
+ # true. This will enable tab completion for file paths relative
58
+ # to the current working directory on systems that support
59
+ # Readline.
60
+ #
61
+ # ==== Example
62
+ # ask("What is your name?")
63
+ #
64
+ # ask("What is your favorite Neopolitan flavor?", :limited_to => ["strawberry", "chocolate", "vanilla"])
65
+ #
66
+ # ask("What is your password?", :echo => false)
67
+ #
68
+ # ask("Where should the file be saved?", :path => true)
69
+ #
70
+ def ask(statement, *args)
71
+ options = args.last.is_a?(Hash) ? args.pop : {}
72
+ color = args.first
73
+
74
+ if options[:limited_to]
75
+ ask_filtered(statement, color, options)
76
+ else
77
+ ask_simply(statement, color, options)
78
+ end
79
+ end
80
+
81
+ # Say (print) something to the user. If the sentence ends with a whitespace
82
+ # or tab character, a new line is not appended (print + flush). Otherwise
83
+ # are passed straight to puts (behavior got from Highline).
84
+ #
85
+ # ==== Example
86
+ # say("I know you knew that.")
87
+ #
88
+ def say(message = "", color = nil, force_new_line = (message.to_s !~ /( |\t)\Z/))
89
+ buffer = prepare_message(message, *color)
90
+ buffer << "\n" if force_new_line && !message.to_s.end_with?("\n")
91
+
92
+ stdout.print(buffer)
93
+ stdout.flush
94
+ end
95
+
96
+ # Say a status with the given color and appends the message. Since this
97
+ # method is used frequently by actions, it allows nil or false to be given
98
+ # in log_status, avoiding the message from being shown. If a Symbol is
99
+ # given in log_status, it's used as the color.
100
+ #
101
+ def say_status(status, message, log_status = true)
102
+ return if quiet? || log_status == false
103
+ spaces = " " * (padding + 1)
104
+ color = log_status.is_a?(Symbol) ? log_status : :green
105
+
106
+ status = status.to_s.rjust(12)
107
+ status = set_color status, color, true if color
108
+
109
+ buffer = "#{status}#{spaces}#{message}"
110
+ buffer << "\n" unless buffer.end_with?("\n")
111
+
112
+ stdout.print(buffer)
113
+ stdout.flush
114
+ end
115
+
116
+ # Make a question the to user and returns true if the user replies "y" or
117
+ # "yes".
118
+ #
119
+ def yes?(statement, color = nil)
120
+ !!(ask(statement, color, :add_to_history => false) =~ is?(:yes))
121
+ end
122
+
123
+ # Make a question the to user and returns true if the user replies "n" or
124
+ # "no".
125
+ #
126
+ def no?(statement, color = nil)
127
+ !!(ask(statement, color, :add_to_history => false) =~ is?(:no))
128
+ end
129
+
130
+ # Prints values in columns
131
+ #
132
+ # ==== Parameters
133
+ # Array[String, String, ...]
134
+ #
135
+ def print_in_columns(array)
136
+ return if array.empty?
137
+ colwidth = (array.map { |el| el.to_s.size }.max || 0) + 2
138
+ array.each_with_index do |value, index|
139
+ # Don't output trailing spaces when printing the last column
140
+ if ((((index + 1) % (terminal_width / colwidth))).zero? && !index.zero?) || index + 1 == array.length
141
+ stdout.puts value
142
+ else
143
+ stdout.printf("%-#{colwidth}s", value)
144
+ end
145
+ end
146
+ end
147
+
148
+ # Prints a table.
149
+ #
150
+ # ==== Parameters
151
+ # Array[Array[String, String, ...]]
152
+ #
153
+ # ==== Options
154
+ # indent<Integer>:: Indent the first column by indent value.
155
+ # colwidth<Integer>:: Force the first column to colwidth spaces wide.
156
+ #
157
+ def print_table(array, options = {}) # rubocop:disable MethodLength
158
+ return if array.empty?
159
+
160
+ formats, indent, colwidth = [], options[:indent].to_i, options[:colwidth]
161
+ options[:truncate] = terminal_width if options[:truncate] == true
162
+
163
+ formats << "%-#{colwidth + 2}s" if colwidth
164
+ start = colwidth ? 1 : 0
165
+
166
+ colcount = array.max { |a, b| a.size <=> b.size }.size
167
+
168
+ maximas = []
169
+
170
+ start.upto(colcount - 1) do |index|
171
+ maxima = array.map { |row| row[index] ? row[index].to_s.size : 0 }.max
172
+ maximas << maxima
173
+ if index == colcount - 1
174
+ # Don't output 2 trailing spaces when printing the last column
175
+ formats << "%-s"
176
+ else
177
+ formats << "%-#{maxima + 2}s"
178
+ end
179
+ end
180
+
181
+ formats[0] = formats[0].insert(0, " " * indent)
182
+ formats << "%s"
183
+
184
+ array.each do |row|
185
+ sentence = ""
186
+
187
+ row.each_with_index do |column, index|
188
+ maxima = maximas[index]
189
+
190
+ if column.is_a?(Numeric)
191
+ if index == row.size - 1
192
+ # Don't output 2 trailing spaces when printing the last column
193
+ f = "%#{maxima}s"
194
+ else
195
+ f = "%#{maxima}s "
196
+ end
197
+ else
198
+ f = formats[index]
199
+ end
200
+ sentence << f % column.to_s
201
+ end
202
+
203
+ sentence = truncate(sentence, options[:truncate]) if options[:truncate]
204
+ stdout.puts sentence
205
+ end
206
+ end
207
+
208
+ # Prints a long string, word-wrapping the text to the current width of the
209
+ # terminal display. Ideal for printing heredocs.
210
+ #
211
+ # ==== Parameters
212
+ # String
213
+ #
214
+ # ==== Options
215
+ # indent<Integer>:: Indent each line of the printed paragraph by indent value.
216
+ #
217
+ def print_wrapped(message, options = {})
218
+ indent = options[:indent] || 0
219
+ width = terminal_width - indent
220
+ paras = message.split("\n\n")
221
+
222
+ paras.map! do |unwrapped|
223
+ unwrapped.strip.gsub(/\n/, " ").squeeze(" ").gsub(/.{1,#{width}}(?:\s|\Z)/) { ($& + 5.chr).gsub(/\n\005/, "\n").gsub(/\005/, "\n") }
224
+ end
225
+
226
+ paras.each do |para|
227
+ para.split("\n").each do |line|
228
+ stdout.puts line.insert(0, " " * indent)
229
+ end
230
+ stdout.puts unless para == paras.last
231
+ end
232
+ end
233
+
234
+ # Deals with file collision and returns true if the file should be
235
+ # overwritten and false otherwise. If a block is given, it uses the block
236
+ # response as the content for the diff.
237
+ #
238
+ # ==== Parameters
239
+ # destination<String>:: the destination file to solve conflicts
240
+ # block<Proc>:: an optional block that returns the value to be used in diff
241
+ #
242
+ def file_collision(destination) # rubocop:disable MethodLength
243
+ return true if @always_force
244
+ options = block_given? ? "[Ynaqdh]" : "[Ynaqh]"
245
+
246
+ loop do
247
+ answer = ask(
248
+ %[Overwrite #{destination}? (enter "h" for help) #{options}],
249
+ :add_to_history => false
250
+ )
251
+
252
+ case answer
253
+ when is?(:yes), is?(:force), ""
254
+ return true
255
+ when is?(:no), is?(:skip)
256
+ return false
257
+ when is?(:always)
258
+ return @always_force = true
259
+ when is?(:quit)
260
+ say "Aborting..."
261
+ fail SystemExit
262
+ when is?(:diff)
263
+ show_diff(destination, yield) if block_given?
264
+ say "Retrying..."
265
+ else
266
+ say file_collision_help
267
+ end
268
+ end
269
+ end
270
+
271
+ # This code was copied from Rake, available under MIT-LICENSE
272
+ # Copyright (c) 2003, 2004 Jim Weirich
273
+ def terminal_width
274
+ if ENV["THOR_COLUMNS"]
275
+ result = ENV["THOR_COLUMNS"].to_i
276
+ else
277
+ result = unix? ? dynamic_width : 80
278
+ end
279
+ result < 10 ? 80 : result
280
+ rescue
281
+ 80
282
+ end
283
+
284
+ # Called if something goes wrong during the execution. This is used by Thor
285
+ # internally and should not be used inside your scripts. If something went
286
+ # wrong, you can always raise an exception. If you raise a Thor::Error, it
287
+ # will be rescued and wrapped in the method below.
288
+ #
289
+ def error(statement)
290
+ stderr.puts statement
291
+ end
292
+
293
+ # Apply color to the given string with optional bold. Disabled in the
294
+ # Thor::Shell::Basic class.
295
+ #
296
+ def set_color(string, *args) #:nodoc:
297
+ string
298
+ end
299
+
300
+ protected
301
+
302
+ def prepare_message(message, *color)
303
+ spaces = " " * padding
304
+ spaces + set_color(message.to_s, *color)
305
+ end
306
+
307
+ def can_display_colors?
308
+ false
309
+ end
310
+
311
+ def lookup_color(color)
312
+ return color unless color.is_a?(Symbol)
313
+ self.class.const_get(color.to_s.upcase)
314
+ end
315
+
316
+ def stdout
317
+ $stdout
318
+ end
319
+
320
+ def stderr
321
+ $stderr
322
+ end
323
+
324
+ def is?(value) #:nodoc:
325
+ value = value.to_s
326
+
327
+ if value.size == 1
328
+ /\A#{value}\z/i
329
+ else
330
+ /\A(#{value}|#{value[0, 1]})\z/i
331
+ end
332
+ end
333
+
334
+ def file_collision_help #:nodoc:
335
+ <<-HELP
336
+ Y - yes, overwrite
337
+ n - no, do not overwrite
338
+ a - all, overwrite this and all others
339
+ q - quit, abort
340
+ d - diff, show the differences between the old and the new
341
+ h - help, show this help
342
+ HELP
343
+ end
344
+
345
+ def show_diff(destination, content) #:nodoc:
346
+ diff_cmd = ENV["THOR_DIFF"] || ENV["RAILS_DIFF"] || "diff -u"
347
+
348
+ Tempfile.open(File.basename(destination), File.dirname(destination)) do |temp|
349
+ temp.write content
350
+ temp.rewind
351
+ system %(#{diff_cmd} "#{destination}" "#{temp.path}")
352
+ end
353
+ end
354
+
355
+ def quiet? #:nodoc:
356
+ mute? || (base && base.options[:quiet])
357
+ end
358
+
359
+ # Calculate the dynamic width of the terminal
360
+ def dynamic_width
361
+ @dynamic_width ||= (dynamic_width_stty.nonzero? || dynamic_width_tput)
362
+ end
363
+
364
+ def dynamic_width_stty
365
+ %x(stty size 2>/dev/null).split[1].to_i
366
+ end
367
+
368
+ def dynamic_width_tput
369
+ %x(tput cols 2>/dev/null).to_i
370
+ end
371
+
372
+ def unix?
373
+ RUBY_PLATFORM =~ /(aix|darwin|linux|(net|free|open)bsd|cygwin|solaris|irix|hpux)/i
374
+ end
375
+
376
+ def truncate(string, width)
377
+ as_unicode do
378
+ chars = string.chars.to_a
379
+ if chars.length <= width
380
+ chars.join
381
+ else
382
+ ( chars[0, width - 3].join) + "..."
383
+ end
384
+ end
385
+ end
386
+
387
+ if "".respond_to?(:encode)
388
+ def as_unicode
389
+ yield
390
+ end
391
+ else
392
+ def as_unicode
393
+ old, $KCODE = $KCODE, "U"
394
+ yield
395
+ ensure
396
+ $KCODE = old
397
+ end
398
+ end
399
+
400
+ def ask_simply(statement, color, options)
401
+ default = options[:default]
402
+ message = [statement, ("(#{default})" if default), nil].uniq.join(" ")
403
+ message = prepare_message(message, *color)
404
+ result = Thor::LineEditor.readline(message, options)
405
+
406
+ return unless result
407
+
408
+ result.strip!
409
+
410
+ if default && result == ""
411
+ default
412
+ else
413
+ result
414
+ end
415
+ end
416
+
417
+ def ask_filtered(statement, color, options)
418
+ answer_set = options[:limited_to]
419
+ correct_answer = nil
420
+ until correct_answer
421
+ answers = answer_set.join(", ")
422
+ answer = ask_simply("#{statement} [#{answers}]", color, options)
423
+ correct_answer = answer_set.include?(answer) ? answer : nil
424
+ say("Your response must be one of: [#{answers}]. Please try again.") unless correct_answer
425
+ end
426
+ correct_answer
427
+ end
428
+ end
429
+ end
430
+ end