thor 0.14.6 → 0.19.4
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.
- checksums.yaml +7 -0
- data/.document +5 -0
- data/{CHANGELOG.rdoc → CHANGELOG.md} +91 -31
- data/CONTRIBUTING.md +15 -0
- data/{LICENSE → LICENSE.md} +2 -2
- data/README.md +36 -296
- data/bin/thor +1 -1
- data/lib/thor/actions/create_file.rb +33 -35
- data/lib/thor/actions/create_link.rb +7 -5
- data/lib/thor/actions/directory.rb +49 -24
- data/lib/thor/actions/empty_directory.rb +68 -67
- data/lib/thor/actions/file_manipulation.rb +85 -28
- data/lib/thor/actions/inject_into_file.rb +26 -32
- data/lib/thor/actions.rb +70 -66
- data/lib/thor/base.rb +314 -237
- data/lib/thor/command.rb +133 -0
- data/lib/thor/core_ext/hash_with_indifferent_access.rb +34 -24
- data/lib/thor/core_ext/io_binary_read.rb +12 -0
- data/lib/thor/core_ext/ordered_hash.rb +94 -65
- data/lib/thor/error.rb +10 -8
- data/lib/thor/group.rb +74 -66
- data/lib/thor/invocation.rb +65 -56
- data/lib/thor/line_editor/basic.rb +35 -0
- data/lib/thor/line_editor/readline.rb +88 -0
- data/lib/thor/line_editor.rb +17 -0
- data/lib/thor/parser/argument.rb +32 -29
- data/lib/thor/parser/arguments.rb +107 -93
- data/lib/thor/parser/option.rb +39 -13
- data/lib/thor/parser/options.rb +144 -97
- data/lib/thor/parser.rb +4 -4
- data/lib/thor/rake_compat.rb +21 -16
- data/lib/thor/runner.rb +166 -153
- data/lib/thor/shell/basic.rb +268 -122
- data/lib/thor/shell/color.rb +84 -43
- data/lib/thor/shell/html.rb +71 -66
- data/lib/thor/shell.rb +30 -37
- data/lib/thor/util.rb +227 -188
- data/lib/thor/version.rb +1 -1
- data/lib/thor.rb +289 -131
- data/thor.gemspec +21 -0
- metadata +50 -189
- data/Thorfile +0 -24
- data/bin/rake2thor +0 -86
- data/lib/thor/core_ext/file_binary_read.rb +0 -9
- data/lib/thor/task.rb +0 -114
- data/spec/actions/create_file_spec.rb +0 -170
- data/spec/actions/directory_spec.rb +0 -136
- data/spec/actions/empty_directory_spec.rb +0 -98
- data/spec/actions/file_manipulation_spec.rb +0 -310
- data/spec/actions/inject_into_file_spec.rb +0 -135
- data/spec/actions_spec.rb +0 -322
- data/spec/base_spec.rb +0 -269
- data/spec/core_ext/hash_with_indifferent_access_spec.rb +0 -43
- data/spec/core_ext/ordered_hash_spec.rb +0 -115
- data/spec/fixtures/application.rb +0 -2
- data/spec/fixtures/bundle/execute.rb +0 -6
- data/spec/fixtures/bundle/main.thor +0 -1
- data/spec/fixtures/doc/%file_name%.rb.tt +0 -1
- data/spec/fixtures/doc/README +0 -3
- data/spec/fixtures/doc/block_helper.rb +0 -3
- data/spec/fixtures/doc/components/.empty_directory +0 -0
- data/spec/fixtures/doc/config.rb +0 -1
- data/spec/fixtures/group.thor +0 -114
- data/spec/fixtures/invoke.thor +0 -112
- data/spec/fixtures/path with spaces +0 -0
- data/spec/fixtures/script.thor +0 -184
- data/spec/fixtures/task.thor +0 -10
- data/spec/group_spec.rb +0 -178
- data/spec/invocation_spec.rb +0 -100
- data/spec/parser/argument_spec.rb +0 -47
- data/spec/parser/arguments_spec.rb +0 -64
- data/spec/parser/option_spec.rb +0 -202
- data/spec/parser/options_spec.rb +0 -319
- data/spec/rake_compat_spec.rb +0 -68
- data/spec/register_spec.rb +0 -92
- data/spec/runner_spec.rb +0 -210
- data/spec/shell/basic_spec.rb +0 -223
- data/spec/shell/color_spec.rb +0 -41
- data/spec/shell/html_spec.rb +0 -27
- data/spec/shell_spec.rb +0 -47
- data/spec/spec_helper.rb +0 -54
- data/spec/task_spec.rb +0 -69
- data/spec/thor_spec.rb +0 -334
- data/spec/util_spec.rb +0 -163
data/lib/thor/shell/basic.rb
CHANGED
|
@@ -1,14 +1,19 @@
|
|
|
1
|
-
require
|
|
1
|
+
require "tempfile"
|
|
2
|
+
require "io/console" if RUBY_VERSION > "1.9.2"
|
|
2
3
|
|
|
3
4
|
class Thor
|
|
4
5
|
module Shell
|
|
5
6
|
class Basic
|
|
6
|
-
attr_accessor :base
|
|
7
|
+
attr_accessor :base
|
|
8
|
+
attr_reader :padding
|
|
7
9
|
|
|
8
|
-
# Initialize base and padding to nil.
|
|
10
|
+
# Initialize base, mute and padding to nil.
|
|
9
11
|
#
|
|
10
12
|
def initialize #:nodoc:
|
|
11
|
-
@base
|
|
13
|
+
@base = nil
|
|
14
|
+
@mute = false
|
|
15
|
+
@padding = 0
|
|
16
|
+
@always_force = false
|
|
12
17
|
end
|
|
13
18
|
|
|
14
19
|
# Mute everything that's inside given block
|
|
@@ -32,14 +37,48 @@ class Thor
|
|
|
32
37
|
@padding = [0, value].max
|
|
33
38
|
end
|
|
34
39
|
|
|
35
|
-
#
|
|
40
|
+
# Sets the output padding while executing a block and resets it.
|
|
41
|
+
#
|
|
42
|
+
def indent(count = 1)
|
|
43
|
+
orig_padding = padding
|
|
44
|
+
self.padding = padding + count
|
|
45
|
+
yield
|
|
46
|
+
self.padding = orig_padding
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Asks something to the user and receives a response.
|
|
50
|
+
#
|
|
51
|
+
# If asked to limit the correct responses, you can pass in an
|
|
52
|
+
# array of acceptable answers. If one of those is not supplied,
|
|
53
|
+
# they will be shown a message stating that one of those answers
|
|
54
|
+
# must be given and re-asked the question.
|
|
55
|
+
#
|
|
56
|
+
# If asking for sensitive information, the :echo option can be set
|
|
57
|
+
# to false to mask user input from $stdin.
|
|
58
|
+
#
|
|
59
|
+
# If the required input is a path, then set the path option to
|
|
60
|
+
# true. This will enable tab completion for file paths relative
|
|
61
|
+
# to the current working directory on systems that support
|
|
62
|
+
# Readline.
|
|
36
63
|
#
|
|
37
64
|
# ==== Example
|
|
38
65
|
# ask("What is your name?")
|
|
39
66
|
#
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
67
|
+
# ask("What is your favorite Neopolitan flavor?", :limited_to => ["strawberry", "chocolate", "vanilla"])
|
|
68
|
+
#
|
|
69
|
+
# ask("What is your password?", :echo => false)
|
|
70
|
+
#
|
|
71
|
+
# ask("Where should the file be saved?", :path => true)
|
|
72
|
+
#
|
|
73
|
+
def ask(statement, *args)
|
|
74
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
|
75
|
+
color = args.first
|
|
76
|
+
|
|
77
|
+
if options[:limited_to]
|
|
78
|
+
ask_filtered(statement, color, options)
|
|
79
|
+
else
|
|
80
|
+
ask_simply(statement, color, options)
|
|
81
|
+
end
|
|
43
82
|
end
|
|
44
83
|
|
|
45
84
|
# Say (print) something to the user. If the sentence ends with a whitespace
|
|
@@ -49,18 +88,12 @@ class Thor
|
|
|
49
88
|
# ==== Example
|
|
50
89
|
# say("I know you knew that.")
|
|
51
90
|
#
|
|
52
|
-
def say(message="", color=nil, force_new_line=(message.to_s !~ /( |\t)
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
spaces = " " * padding
|
|
91
|
+
def say(message = "", color = nil, force_new_line = (message.to_s !~ /( |\t)\Z/))
|
|
92
|
+
buffer = prepare_message(message, *color)
|
|
93
|
+
buffer << "\n" if force_new_line && !message.to_s.end_with?("\n")
|
|
57
94
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
else
|
|
61
|
-
$stdout.print(spaces + message)
|
|
62
|
-
end
|
|
63
|
-
$stdout.flush
|
|
95
|
+
stdout.print(buffer)
|
|
96
|
+
stdout.flush
|
|
64
97
|
end
|
|
65
98
|
|
|
66
99
|
# Say a status with the given color and appends the message. Since this
|
|
@@ -68,7 +101,7 @@ class Thor
|
|
|
68
101
|
# in log_status, avoiding the message from being shown. If a Symbol is
|
|
69
102
|
# given in log_status, it's used as the color.
|
|
70
103
|
#
|
|
71
|
-
def say_status(status, message, log_status=true)
|
|
104
|
+
def say_status(status, message, log_status = true)
|
|
72
105
|
return if quiet? || log_status == false
|
|
73
106
|
spaces = " " * (padding + 1)
|
|
74
107
|
color = log_status.is_a?(Symbol) ? log_status : :green
|
|
@@ -76,22 +109,43 @@ class Thor
|
|
|
76
109
|
status = status.to_s.rjust(12)
|
|
77
110
|
status = set_color status, color, true if color
|
|
78
111
|
|
|
79
|
-
|
|
80
|
-
|
|
112
|
+
buffer = "#{status}#{spaces}#{message}"
|
|
113
|
+
buffer << "\n" unless buffer.end_with?("\n")
|
|
114
|
+
|
|
115
|
+
stdout.print(buffer)
|
|
116
|
+
stdout.flush
|
|
81
117
|
end
|
|
82
118
|
|
|
83
119
|
# Make a question the to user and returns true if the user replies "y" or
|
|
84
120
|
# "yes".
|
|
85
121
|
#
|
|
86
|
-
def yes?(statement, color=nil)
|
|
87
|
-
!!(ask(statement, color) =~ is?(:yes))
|
|
122
|
+
def yes?(statement, color = nil)
|
|
123
|
+
!!(ask(statement, color, :add_to_history => false) =~ is?(:yes))
|
|
88
124
|
end
|
|
89
125
|
|
|
90
126
|
# Make a question the to user and returns true if the user replies "n" or
|
|
91
127
|
# "no".
|
|
92
128
|
#
|
|
93
|
-
def no?(statement, color=nil)
|
|
94
|
-
|
|
129
|
+
def no?(statement, color = nil)
|
|
130
|
+
!!(ask(statement, color, :add_to_history => false) =~ is?(:no))
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Prints values in columns
|
|
134
|
+
#
|
|
135
|
+
# ==== Parameters
|
|
136
|
+
# Array[String, String, ...]
|
|
137
|
+
#
|
|
138
|
+
def print_in_columns(array)
|
|
139
|
+
return if array.empty?
|
|
140
|
+
colwidth = (array.map { |el| el.to_s.size }.max || 0) + 2
|
|
141
|
+
array.each_with_index do |value, index|
|
|
142
|
+
# Don't output trailing spaces when printing the last column
|
|
143
|
+
if ((((index + 1) % (terminal_width / colwidth))).zero? && !index.zero?) || index + 1 == array.length
|
|
144
|
+
stdout.puts value
|
|
145
|
+
else
|
|
146
|
+
stdout.printf("%-#{colwidth}s", value)
|
|
147
|
+
end
|
|
148
|
+
end
|
|
95
149
|
end
|
|
96
150
|
|
|
97
151
|
# Prints a table.
|
|
@@ -100,35 +154,59 @@ class Thor
|
|
|
100
154
|
# Array[Array[String, String, ...]]
|
|
101
155
|
#
|
|
102
156
|
# ==== Options
|
|
103
|
-
#
|
|
157
|
+
# indent<Integer>:: Indent the first column by indent value.
|
|
104
158
|
# colwidth<Integer>:: Force the first column to colwidth spaces wide.
|
|
105
159
|
#
|
|
106
|
-
def print_table(
|
|
107
|
-
return if
|
|
160
|
+
def print_table(array, options = {}) # rubocop:disable MethodLength
|
|
161
|
+
return if array.empty?
|
|
108
162
|
|
|
109
|
-
formats
|
|
163
|
+
formats = []
|
|
164
|
+
indent = options[:indent].to_i
|
|
165
|
+
colwidth = options[:colwidth]
|
|
110
166
|
options[:truncate] = terminal_width if options[:truncate] == true
|
|
111
167
|
|
|
112
168
|
formats << "%-#{colwidth + 2}s" if colwidth
|
|
113
169
|
start = colwidth ? 1 : 0
|
|
114
170
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
171
|
+
colcount = array.max { |a, b| a.size <=> b.size }.size
|
|
172
|
+
|
|
173
|
+
maximas = []
|
|
174
|
+
|
|
175
|
+
start.upto(colcount - 1) do |index|
|
|
176
|
+
maxima = array.map { |row| row[index] ? row[index].to_s.size : 0 }.max
|
|
177
|
+
maximas << maxima
|
|
178
|
+
formats << if index == colcount - 1
|
|
179
|
+
# Don't output 2 trailing spaces when printing the last column
|
|
180
|
+
"%-s"
|
|
181
|
+
else
|
|
182
|
+
"%-#{maxima + 2}s"
|
|
183
|
+
end
|
|
118
184
|
end
|
|
119
185
|
|
|
120
|
-
formats[0] = formats[0].insert(0, " " *
|
|
186
|
+
formats[0] = formats[0].insert(0, " " * indent)
|
|
121
187
|
formats << "%s"
|
|
122
188
|
|
|
123
|
-
|
|
189
|
+
array.each do |row|
|
|
124
190
|
sentence = ""
|
|
125
191
|
|
|
126
|
-
row.each_with_index do |column,
|
|
127
|
-
|
|
192
|
+
row.each_with_index do |column, index|
|
|
193
|
+
maxima = maximas[index]
|
|
194
|
+
|
|
195
|
+
f = if column.is_a?(Numeric)
|
|
196
|
+
if index == row.size - 1
|
|
197
|
+
# Don't output 2 trailing spaces when printing the last column
|
|
198
|
+
"%#{maxima}s"
|
|
199
|
+
else
|
|
200
|
+
"%#{maxima}s "
|
|
201
|
+
end
|
|
202
|
+
else
|
|
203
|
+
formats[index]
|
|
204
|
+
end
|
|
205
|
+
sentence << f % column.to_s
|
|
128
206
|
end
|
|
129
207
|
|
|
130
208
|
sentence = truncate(sentence, options[:truncate]) if options[:truncate]
|
|
131
|
-
|
|
209
|
+
stdout.puts sentence
|
|
132
210
|
end
|
|
133
211
|
end
|
|
134
212
|
|
|
@@ -139,29 +217,27 @@ class Thor
|
|
|
139
217
|
# String
|
|
140
218
|
#
|
|
141
219
|
# ==== Options
|
|
142
|
-
#
|
|
220
|
+
# indent<Integer>:: Indent each line of the printed paragraph by indent value.
|
|
143
221
|
#
|
|
144
|
-
def print_wrapped(message, options={})
|
|
145
|
-
|
|
146
|
-
width = terminal_width -
|
|
222
|
+
def print_wrapped(message, options = {})
|
|
223
|
+
indent = options[:indent] || 0
|
|
224
|
+
width = terminal_width - indent
|
|
147
225
|
paras = message.split("\n\n")
|
|
148
226
|
|
|
149
227
|
paras.map! do |unwrapped|
|
|
150
|
-
unwrapped.strip.
|
|
151
|
-
gsub(/.{1,#{width}}(?:\s|\Z)/){($& + 5.chr).
|
|
152
|
-
gsub(/\n\005/,"\n").gsub(/\005/,"\n")}
|
|
228
|
+
unwrapped.strip.tr("\n", " ").squeeze(" ").gsub(/.{1,#{width}}(?:\s|\Z)/) { ($& + 5.chr).gsub(/\n\005/, "\n").gsub(/\005/, "\n") }
|
|
153
229
|
end
|
|
154
230
|
|
|
155
231
|
paras.each do |para|
|
|
156
232
|
para.split("\n").each do |line|
|
|
157
|
-
|
|
233
|
+
stdout.puts line.insert(0, " " * indent)
|
|
158
234
|
end
|
|
159
|
-
|
|
235
|
+
stdout.puts unless para == paras.last
|
|
160
236
|
end
|
|
161
237
|
end
|
|
162
238
|
|
|
163
239
|
# Deals with file collision and returns true if the file should be
|
|
164
|
-
#
|
|
240
|
+
# overwritten and false otherwise. If a block is given, it uses the block
|
|
165
241
|
# response as the content for the diff.
|
|
166
242
|
#
|
|
167
243
|
# ==== Parameters
|
|
@@ -172,119 +248,189 @@ class Thor
|
|
|
172
248
|
return true if @always_force
|
|
173
249
|
options = block_given? ? "[Ynaqdh]" : "[Ynaqh]"
|
|
174
250
|
|
|
175
|
-
|
|
176
|
-
answer = ask
|
|
251
|
+
loop do
|
|
252
|
+
answer = ask(
|
|
253
|
+
%[Overwrite #{destination}? (enter "h" for help) #{options}],
|
|
254
|
+
:add_to_history => false
|
|
255
|
+
)
|
|
177
256
|
|
|
178
257
|
case answer
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
258
|
+
when is?(:yes), is?(:force), ""
|
|
259
|
+
return true
|
|
260
|
+
when is?(:no), is?(:skip)
|
|
261
|
+
return false
|
|
262
|
+
when is?(:always)
|
|
263
|
+
return @always_force = true
|
|
264
|
+
when is?(:quit)
|
|
265
|
+
say "Aborting..."
|
|
266
|
+
raise SystemExit
|
|
267
|
+
when is?(:diff)
|
|
268
|
+
show_diff(destination, yield) if block_given?
|
|
269
|
+
say "Retrying..."
|
|
270
|
+
else
|
|
271
|
+
say file_collision_help
|
|
193
272
|
end
|
|
194
273
|
end
|
|
195
274
|
end
|
|
196
275
|
|
|
276
|
+
# This code was copied from Rake, available under MIT-LICENSE
|
|
277
|
+
# Copyright (c) 2003, 2004 Jim Weirich
|
|
278
|
+
def terminal_width
|
|
279
|
+
result = if ENV["THOR_COLUMNS"]
|
|
280
|
+
ENV["THOR_COLUMNS"].to_i
|
|
281
|
+
else
|
|
282
|
+
unix? ? dynamic_width : 80
|
|
283
|
+
end
|
|
284
|
+
result < 10 ? 80 : result
|
|
285
|
+
rescue
|
|
286
|
+
80
|
|
287
|
+
end
|
|
288
|
+
|
|
197
289
|
# Called if something goes wrong during the execution. This is used by Thor
|
|
198
|
-
# internally and should not be used inside your scripts. If
|
|
290
|
+
# internally and should not be used inside your scripts. If something went
|
|
199
291
|
# wrong, you can always raise an exception. If you raise a Thor::Error, it
|
|
200
292
|
# will be rescued and wrapped in the method below.
|
|
201
293
|
#
|
|
202
294
|
def error(statement)
|
|
203
|
-
|
|
295
|
+
stderr.puts statement
|
|
204
296
|
end
|
|
205
297
|
|
|
206
298
|
# Apply color to the given string with optional bold. Disabled in the
|
|
207
299
|
# Thor::Shell::Basic class.
|
|
208
300
|
#
|
|
209
|
-
def set_color(string,
|
|
301
|
+
def set_color(string, *) #:nodoc:
|
|
210
302
|
string
|
|
211
303
|
end
|
|
212
304
|
|
|
213
|
-
|
|
305
|
+
protected
|
|
306
|
+
|
|
307
|
+
def prepare_message(message, *color)
|
|
308
|
+
spaces = " " * padding
|
|
309
|
+
spaces + set_color(message.to_s, *color)
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
def can_display_colors?
|
|
313
|
+
false
|
|
314
|
+
end
|
|
214
315
|
|
|
215
|
-
|
|
216
|
-
|
|
316
|
+
def lookup_color(color)
|
|
317
|
+
return color unless color.is_a?(Symbol)
|
|
318
|
+
self.class.const_get(color.to_s.upcase)
|
|
319
|
+
end
|
|
217
320
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
/\A(#{value}|#{value[0,1]})\z/i
|
|
222
|
-
end
|
|
223
|
-
end
|
|
321
|
+
def stdout
|
|
322
|
+
$stdout
|
|
323
|
+
end
|
|
224
324
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
n - no, do not overwrite
|
|
229
|
-
a - all, overwrite this and all others
|
|
230
|
-
q - quit, abort
|
|
231
|
-
d - diff, show the differences between the old and the new
|
|
232
|
-
h - help, show this help
|
|
233
|
-
HELP
|
|
234
|
-
end
|
|
325
|
+
def stderr
|
|
326
|
+
$stderr
|
|
327
|
+
end
|
|
235
328
|
|
|
236
|
-
|
|
237
|
-
|
|
329
|
+
def is?(value) #:nodoc:
|
|
330
|
+
value = value.to_s
|
|
238
331
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
end
|
|
332
|
+
if value.size == 1
|
|
333
|
+
/\A#{value}\z/i
|
|
334
|
+
else
|
|
335
|
+
/\A(#{value}|#{value[0, 1]})\z/i
|
|
244
336
|
end
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
def file_collision_help #:nodoc:
|
|
340
|
+
<<-HELP
|
|
341
|
+
Y - yes, overwrite
|
|
342
|
+
n - no, do not overwrite
|
|
343
|
+
a - all, overwrite this and all others
|
|
344
|
+
q - quit, abort
|
|
345
|
+
d - diff, show the differences between the old and the new
|
|
346
|
+
h - help, show this help
|
|
347
|
+
HELP
|
|
348
|
+
end
|
|
245
349
|
|
|
246
|
-
|
|
247
|
-
|
|
350
|
+
def show_diff(destination, content) #:nodoc:
|
|
351
|
+
diff_cmd = ENV["THOR_DIFF"] || ENV["RAILS_DIFF"] || "diff -u"
|
|
352
|
+
|
|
353
|
+
Tempfile.open(File.basename(destination), File.dirname(destination)) do |temp|
|
|
354
|
+
temp.write content
|
|
355
|
+
temp.rewind
|
|
356
|
+
system %(#{diff_cmd} "#{destination}" "#{temp.path}")
|
|
248
357
|
end
|
|
358
|
+
end
|
|
249
359
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
360
|
+
def quiet? #:nodoc:
|
|
361
|
+
mute? || (base && base.options[:quiet])
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
# Calculate the dynamic width of the terminal
|
|
365
|
+
def dynamic_width
|
|
366
|
+
@dynamic_width ||= (dynamic_width_stty.nonzero? || dynamic_width_tput)
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
def dynamic_width_stty
|
|
370
|
+
`stty size 2>/dev/null`.split[1].to_i
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
def dynamic_width_tput
|
|
374
|
+
`tput cols 2>/dev/null`.to_i
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
def unix?
|
|
378
|
+
RUBY_PLATFORM =~ /(aix|darwin|linux|(net|free|open)bsd|cygwin|solaris|irix|hpux)/i
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
def truncate(string, width)
|
|
382
|
+
as_unicode do
|
|
383
|
+
chars = string.chars.to_a
|
|
384
|
+
if chars.length <= width
|
|
385
|
+
chars.join
|
|
255
386
|
else
|
|
256
|
-
|
|
387
|
+
chars[0, width - 3].join + "..."
|
|
257
388
|
end
|
|
258
|
-
(result < 10) ? 80 : result
|
|
259
|
-
rescue
|
|
260
|
-
80
|
|
261
389
|
end
|
|
390
|
+
end
|
|
262
391
|
|
|
263
|
-
|
|
264
|
-
def
|
|
265
|
-
|
|
392
|
+
if "".respond_to?(:encode)
|
|
393
|
+
def as_unicode
|
|
394
|
+
yield
|
|
266
395
|
end
|
|
267
|
-
|
|
268
|
-
def
|
|
269
|
-
|
|
396
|
+
else
|
|
397
|
+
def as_unicode
|
|
398
|
+
old = $KCODE
|
|
399
|
+
$KCODE = "U"
|
|
400
|
+
yield
|
|
401
|
+
ensure
|
|
402
|
+
$KCODE = old
|
|
270
403
|
end
|
|
404
|
+
end
|
|
271
405
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
406
|
+
def ask_simply(statement, color, options)
|
|
407
|
+
default = options[:default]
|
|
408
|
+
message = [statement, ("(#{default})" if default), nil].uniq.join(" ")
|
|
409
|
+
message = prepare_message(message, *color)
|
|
410
|
+
result = Thor::LineEditor.readline(message, options)
|
|
275
411
|
|
|
276
|
-
|
|
277
|
-
RUBY_PLATFORM =~ /(aix|darwin|linux|(net|free|open)bsd|cygwin|solaris|irix|hpux)/i
|
|
278
|
-
end
|
|
412
|
+
return unless result
|
|
279
413
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
414
|
+
result.strip!
|
|
415
|
+
|
|
416
|
+
if default && result == ""
|
|
417
|
+
default
|
|
418
|
+
else
|
|
419
|
+
result
|
|
286
420
|
end
|
|
421
|
+
end
|
|
287
422
|
|
|
423
|
+
def ask_filtered(statement, color, options)
|
|
424
|
+
answer_set = options[:limited_to]
|
|
425
|
+
correct_answer = nil
|
|
426
|
+
until correct_answer
|
|
427
|
+
answers = answer_set.join(", ")
|
|
428
|
+
answer = ask_simply("#{statement} [#{answers}]", color, options)
|
|
429
|
+
correct_answer = answer_set.include?(answer) ? answer : nil
|
|
430
|
+
say("Your response must be one of: [#{answers}]. Please try again.") unless correct_answer
|
|
431
|
+
end
|
|
432
|
+
correct_answer
|
|
433
|
+
end
|
|
288
434
|
end
|
|
289
435
|
end
|
|
290
436
|
end
|
data/lib/thor/shell/color.rb
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
require
|
|
1
|
+
require "thor/shell/basic"
|
|
2
2
|
|
|
3
3
|
class Thor
|
|
4
4
|
module Shell
|
|
@@ -50,59 +50,100 @@ class Thor
|
|
|
50
50
|
# on Highline implementation and it automatically appends CLEAR to the end
|
|
51
51
|
# of the returned String.
|
|
52
52
|
#
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
53
|
+
# Pass foreground, background and bold options to this method as
|
|
54
|
+
# symbols.
|
|
55
|
+
#
|
|
56
|
+
# Example:
|
|
57
|
+
#
|
|
58
|
+
# set_color "Hi!", :red, :on_white, :bold
|
|
59
|
+
#
|
|
60
|
+
# The available colors are:
|
|
61
|
+
#
|
|
62
|
+
# :bold
|
|
63
|
+
# :black
|
|
64
|
+
# :red
|
|
65
|
+
# :green
|
|
66
|
+
# :yellow
|
|
67
|
+
# :blue
|
|
68
|
+
# :magenta
|
|
69
|
+
# :cyan
|
|
70
|
+
# :white
|
|
71
|
+
# :on_black
|
|
72
|
+
# :on_red
|
|
73
|
+
# :on_green
|
|
74
|
+
# :on_yellow
|
|
75
|
+
# :on_blue
|
|
76
|
+
# :on_magenta
|
|
77
|
+
# :on_cyan
|
|
78
|
+
# :on_white
|
|
79
|
+
def set_color(string, *colors)
|
|
80
|
+
if colors.compact.empty? || !can_display_colors?
|
|
81
|
+
string
|
|
82
|
+
elsif colors.all? { |color| color.is_a?(Symbol) || color.is_a?(String) }
|
|
83
|
+
ansi_colors = colors.map { |color| lookup_color(color) }
|
|
84
|
+
"#{ansi_colors.join}#{string}#{CLEAR}"
|
|
85
|
+
else
|
|
86
|
+
# The old API was `set_color(color, bold=boolean)`. We
|
|
87
|
+
# continue to support the old API because you should never
|
|
88
|
+
# break old APIs unnecessarily :P
|
|
89
|
+
foreground, bold = colors
|
|
90
|
+
foreground = self.class.const_get(foreground.to_s.upcase) if foreground.is_a?(Symbol)
|
|
91
|
+
|
|
92
|
+
bold = bold ? BOLD : ""
|
|
93
|
+
"#{bold}#{foreground}#{string}#{CLEAR}"
|
|
94
|
+
end
|
|
57
95
|
end
|
|
58
96
|
|
|
59
|
-
|
|
97
|
+
protected
|
|
98
|
+
|
|
99
|
+
def can_display_colors?
|
|
100
|
+
stdout.tty?
|
|
101
|
+
end
|
|
60
102
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
103
|
+
# Overwrite show_diff to show diff with colors if Diff::LCS is
|
|
104
|
+
# available.
|
|
105
|
+
#
|
|
106
|
+
def show_diff(destination, content) #:nodoc:
|
|
107
|
+
if diff_lcs_loaded? && ENV["THOR_DIFF"].nil? && ENV["RAILS_DIFF"].nil?
|
|
108
|
+
actual = File.binread(destination).to_s.split("\n")
|
|
109
|
+
content = content.to_s.split("\n")
|
|
68
110
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
end
|
|
72
|
-
else
|
|
73
|
-
super
|
|
111
|
+
Diff::LCS.sdiff(actual, content).each do |diff|
|
|
112
|
+
output_diff_line(diff)
|
|
74
113
|
end
|
|
114
|
+
else
|
|
115
|
+
super
|
|
75
116
|
end
|
|
117
|
+
end
|
|
76
118
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
end
|
|
119
|
+
def output_diff_line(diff) #:nodoc:
|
|
120
|
+
case diff.action
|
|
121
|
+
when "-"
|
|
122
|
+
say "- #{diff.old_element.chomp}", :red, true
|
|
123
|
+
when "+"
|
|
124
|
+
say "+ #{diff.new_element.chomp}", :green, true
|
|
125
|
+
when "!"
|
|
126
|
+
say "- #{diff.old_element.chomp}", :red, true
|
|
127
|
+
say "+ #{diff.new_element.chomp}", :green, true
|
|
128
|
+
else
|
|
129
|
+
say " #{diff.old_element.chomp}", nil, true
|
|
89
130
|
end
|
|
131
|
+
end
|
|
90
132
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
133
|
+
# Check if Diff::LCS is loaded. If it is, use it to create pretty output
|
|
134
|
+
# for diff.
|
|
135
|
+
#
|
|
136
|
+
def diff_lcs_loaded? #:nodoc:
|
|
137
|
+
return true if defined?(Diff::LCS)
|
|
138
|
+
return @diff_lcs_loaded unless @diff_lcs_loaded.nil?
|
|
97
139
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
end
|
|
140
|
+
@diff_lcs_loaded = begin
|
|
141
|
+
require "diff/lcs"
|
|
142
|
+
true
|
|
143
|
+
rescue LoadError
|
|
144
|
+
false
|
|
104
145
|
end
|
|
105
|
-
|
|
146
|
+
end
|
|
106
147
|
end
|
|
107
148
|
end
|
|
108
149
|
end
|