thor 0.19.4 → 1.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/README.md +7 -9
- data/lib/thor/actions/create_file.rb +2 -1
- data/lib/thor/actions/create_link.rb +4 -2
- data/lib/thor/actions/directory.rb +7 -17
- data/lib/thor/actions/empty_directory.rb +9 -1
- data/lib/thor/actions/file_manipulation.rb +64 -16
- data/lib/thor/actions/inject_into_file.rb +27 -11
- data/lib/thor/actions.rb +38 -16
- data/lib/thor/base.rb +84 -41
- data/lib/thor/command.rb +30 -21
- data/lib/thor/core_ext/hash_with_indifferent_access.rb +18 -0
- data/lib/thor/error.rb +83 -0
- data/lib/thor/group.rb +4 -4
- data/lib/thor/invocation.rb +1 -0
- data/lib/thor/line_editor/basic.rb +2 -0
- data/lib/thor/line_editor/readline.rb +6 -6
- data/lib/thor/line_editor.rb +2 -2
- data/lib/thor/nested_context.rb +29 -0
- data/lib/thor/parser/arguments.rb +7 -3
- data/lib/thor/parser/option.rb +22 -9
- data/lib/thor/parser/options.rb +45 -10
- data/lib/thor/parser.rb +4 -4
- data/lib/thor/rake_compat.rb +1 -0
- data/lib/thor/runner.rb +9 -6
- data/lib/thor/shell/basic.rb +96 -20
- data/lib/thor/shell/color.rb +10 -2
- data/lib/thor/shell/html.rb +3 -3
- data/lib/thor/shell.rb +5 -5
- data/lib/thor/util.rb +18 -2
- data/lib/thor/version.rb +1 -1
- data/lib/thor.rb +39 -15
- data/thor.gemspec +10 -2
- metadata +20 -11
- data/CHANGELOG.md +0 -163
- data/lib/thor/core_ext/io_binary_read.rb +0 -12
- data/lib/thor/core_ext/ordered_hash.rb +0 -129
data/lib/thor/parser/option.rb
CHANGED
@@ -1,16 +1,18 @@
|
|
1
1
|
class Thor
|
2
2
|
class Option < Argument #:nodoc:
|
3
|
-
attr_reader :aliases, :group, :lazy_default, :hide
|
3
|
+
attr_reader :aliases, :group, :lazy_default, :hide, :repeatable
|
4
4
|
|
5
5
|
VALID_TYPES = [:boolean, :numeric, :hash, :array, :string]
|
6
6
|
|
7
7
|
def initialize(name, options = {})
|
8
|
+
@check_default_type = options[:check_default_type]
|
8
9
|
options[:required] = false unless options.key?(:required)
|
10
|
+
@repeatable = options.fetch(:repeatable, false)
|
9
11
|
super
|
10
|
-
@lazy_default
|
11
|
-
@group
|
12
|
-
@aliases
|
13
|
-
@hide
|
12
|
+
@lazy_default = options[:lazy_default]
|
13
|
+
@group = options[:group].to_s.capitalize if options[:group]
|
14
|
+
@aliases = Array(options[:aliases])
|
15
|
+
@hide = options[:hide]
|
14
16
|
end
|
15
17
|
|
16
18
|
# This parse quick options given as method_options. It makes several
|
@@ -80,12 +82,12 @@ class Thor
|
|
80
82
|
|
81
83
|
def usage(padding = 0)
|
82
84
|
sample = if banner && !banner.to_s.empty?
|
83
|
-
"#{switch_name}=#{banner}"
|
85
|
+
"#{switch_name}=#{banner}".dup
|
84
86
|
else
|
85
87
|
switch_name
|
86
88
|
end
|
87
89
|
|
88
|
-
sample = "[#{sample}]" unless required?
|
90
|
+
sample = "[#{sample}]".dup unless required?
|
89
91
|
|
90
92
|
if boolean?
|
91
93
|
sample << ", [#{dasherize('no-' + human_name)}]" unless (name == "force") || name.start_with?("no-")
|
@@ -127,8 +129,19 @@ class Thor
|
|
127
129
|
@default.class.name.downcase.to_sym
|
128
130
|
end
|
129
131
|
|
130
|
-
|
131
|
-
|
132
|
+
expected_type = (@repeatable && @type != :hash) ? :array : @type
|
133
|
+
|
134
|
+
if default_type != expected_type
|
135
|
+
err = "Expected #{expected_type} default value for '#{switch_name}'; got #{@default.inspect} (#{default_type})"
|
136
|
+
|
137
|
+
if @check_default_type
|
138
|
+
raise ArgumentError, err
|
139
|
+
elsif @check_default_type == nil
|
140
|
+
Thor.deprecation_warning "#{err}.\n" +
|
141
|
+
'This will be rejected in the future unless you explicitly pass the options `check_default_type: false`' +
|
142
|
+
' or call `allow_incompatible_default_type!` in your code'
|
143
|
+
end
|
144
|
+
end
|
132
145
|
end
|
133
146
|
|
134
147
|
def dasherized?
|
data/lib/thor/parser/options.rb
CHANGED
@@ -18,19 +18,20 @@ class Thor
|
|
18
18
|
when Hash
|
19
19
|
"--#{key} #{value.map { |k, v| "#{k}:#{v}" }.join(' ')}"
|
20
20
|
when nil, false
|
21
|
-
|
21
|
+
nil
|
22
22
|
else
|
23
23
|
"--#{key} #{value.inspect}"
|
24
24
|
end
|
25
|
-
end.join(" ")
|
25
|
+
end.compact.join(" ")
|
26
26
|
end
|
27
27
|
|
28
28
|
# Takes a hash of Thor::Option and a hash with defaults.
|
29
29
|
#
|
30
30
|
# If +stop_on_unknown+ is true, #parse will stop as soon as it encounters
|
31
31
|
# an unknown option or a regular argument.
|
32
|
-
def initialize(hash_options = {}, defaults = {}, stop_on_unknown = false)
|
32
|
+
def initialize(hash_options = {}, defaults = {}, stop_on_unknown = false, disable_required_check = false)
|
33
33
|
@stop_on_unknown = stop_on_unknown
|
34
|
+
@disable_required_check = disable_required_check
|
34
35
|
options = hash_options.values
|
35
36
|
super(options)
|
36
37
|
|
@@ -43,6 +44,8 @@ class Thor
|
|
43
44
|
@shorts = {}
|
44
45
|
@switches = {}
|
45
46
|
@extra = []
|
47
|
+
@stopped_parsing_after_extra_index = nil
|
48
|
+
@is_treated_as_value = false
|
46
49
|
|
47
50
|
options.each do |option|
|
48
51
|
@switches[option.switch_name] = option
|
@@ -65,14 +68,26 @@ class Thor
|
|
65
68
|
if result == OPTS_END
|
66
69
|
shift
|
67
70
|
@parsing_options = false
|
71
|
+
@stopped_parsing_after_extra_index ||= @extra.size
|
68
72
|
super
|
69
73
|
else
|
70
74
|
result
|
71
75
|
end
|
72
76
|
end
|
73
77
|
|
78
|
+
def shift
|
79
|
+
@is_treated_as_value = false
|
80
|
+
super
|
81
|
+
end
|
82
|
+
|
83
|
+
def unshift(arg, is_value: false)
|
84
|
+
@is_treated_as_value = is_value
|
85
|
+
super(arg)
|
86
|
+
end
|
87
|
+
|
74
88
|
def parse(args) # rubocop:disable MethodLength
|
75
89
|
@pile = args.dup
|
90
|
+
@is_treated_as_value = false
|
76
91
|
@parsing_options = true
|
77
92
|
|
78
93
|
while peek
|
@@ -85,7 +100,10 @@ class Thor
|
|
85
100
|
when SHORT_SQ_RE
|
86
101
|
unshift($1.split("").map { |f| "-#{f}" })
|
87
102
|
next
|
88
|
-
when EQ_RE
|
103
|
+
when EQ_RE
|
104
|
+
unshift($2, is_value: true)
|
105
|
+
switch = $1
|
106
|
+
when SHORT_NUM
|
89
107
|
unshift($2)
|
90
108
|
switch = $1
|
91
109
|
when LONG_RE, SHORT_RE
|
@@ -94,10 +112,12 @@ class Thor
|
|
94
112
|
|
95
113
|
switch = normalize_switch(switch)
|
96
114
|
option = switch_option(switch)
|
97
|
-
|
115
|
+
result = parse_peek(switch, option)
|
116
|
+
assign_result!(option, result)
|
98
117
|
elsif @stop_on_unknown
|
99
118
|
@parsing_options = false
|
100
119
|
@extra << shifted
|
120
|
+
@stopped_parsing_after_extra_index ||= @extra.size
|
101
121
|
@extra << shift while peek
|
102
122
|
break
|
103
123
|
elsif match
|
@@ -111,7 +131,7 @@ class Thor
|
|
111
131
|
end
|
112
132
|
end
|
113
133
|
|
114
|
-
check_requirement!
|
134
|
+
check_requirement! unless @disable_required_check
|
115
135
|
|
116
136
|
assigns = Thor::CoreExt::HashWithIndifferentAccess.new(@assigns)
|
117
137
|
assigns.freeze
|
@@ -119,18 +139,31 @@ class Thor
|
|
119
139
|
end
|
120
140
|
|
121
141
|
def check_unknown!
|
142
|
+
to_check = @stopped_parsing_after_extra_index ? @extra[0...@stopped_parsing_after_extra_index] : @extra
|
143
|
+
|
122
144
|
# an unknown option starts with - or -- and has no more --'s afterward.
|
123
|
-
unknown =
|
124
|
-
raise UnknownArgumentError
|
145
|
+
unknown = to_check.select { |str| str =~ /^--?(?:(?!--).)*$/ }
|
146
|
+
raise UnknownArgumentError.new(@switches.keys, unknown) unless unknown.empty?
|
125
147
|
end
|
126
148
|
|
127
149
|
protected
|
128
150
|
|
151
|
+
def assign_result!(option, result)
|
152
|
+
if option.repeatable && option.type == :hash
|
153
|
+
(@assigns[option.human_name] ||= {}).merge!(result)
|
154
|
+
elsif option.repeatable
|
155
|
+
(@assigns[option.human_name] ||= []) << result
|
156
|
+
else
|
157
|
+
@assigns[option.human_name] = result
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
129
161
|
# Check if the current value in peek is a registered switch.
|
130
162
|
#
|
131
163
|
# Two booleans are returned. The first is true if the current value
|
132
164
|
# starts with a hyphen; the second is true if it is a registered switch.
|
133
165
|
def current_is_switch?
|
166
|
+
return [false, false] if @is_treated_as_value
|
134
167
|
case peek
|
135
168
|
when LONG_RE, SHORT_RE, EQ_RE, SHORT_NUM
|
136
169
|
[true, switch?($1)]
|
@@ -142,6 +175,7 @@ class Thor
|
|
142
175
|
end
|
143
176
|
|
144
177
|
def current_is_switch_formatted?
|
178
|
+
return false if @is_treated_as_value
|
145
179
|
case peek
|
146
180
|
when LONG_RE, SHORT_RE, EQ_RE, SHORT_NUM, SHORT_SQ_RE
|
147
181
|
true
|
@@ -151,11 +185,12 @@ class Thor
|
|
151
185
|
end
|
152
186
|
|
153
187
|
def current_is_value?
|
188
|
+
return true if @is_treated_as_value
|
154
189
|
peek && (!parsing_options? || super)
|
155
190
|
end
|
156
191
|
|
157
192
|
def switch?(arg)
|
158
|
-
switch_option(normalize_switch(arg))
|
193
|
+
!switch_option(normalize_switch(arg)).nil?
|
159
194
|
end
|
160
195
|
|
161
196
|
def switch_option(arg)
|
@@ -188,7 +223,7 @@ class Thor
|
|
188
223
|
shift
|
189
224
|
false
|
190
225
|
else
|
191
|
-
|
226
|
+
@switches.key?(switch) || !no_or_skip?(switch)
|
192
227
|
end
|
193
228
|
else
|
194
229
|
@switches.key?(switch) || !no_or_skip?(switch)
|
data/lib/thor/parser.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
1
|
+
require_relative "parser/argument"
|
2
|
+
require_relative "parser/arguments"
|
3
|
+
require_relative "parser/option"
|
4
|
+
require_relative "parser/options"
|
data/lib/thor/rake_compat.rb
CHANGED
@@ -25,6 +25,7 @@ class Thor
|
|
25
25
|
end
|
26
26
|
|
27
27
|
def self.included(base)
|
28
|
+
super(base)
|
28
29
|
# Hack. Make rakefile point to invoker, so rdoc task is generated properly.
|
29
30
|
rakefile = File.basename(caller[0].match(/(.*):\d+/)[1])
|
30
31
|
Rake.application.instance_variable_set(:@rakefile, rakefile)
|
data/lib/thor/runner.rb
CHANGED
@@ -1,14 +1,13 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require "thor/core_ext/io_binary_read"
|
1
|
+
require_relative "../thor"
|
2
|
+
require_relative "group"
|
4
3
|
|
5
|
-
require "fileutils"
|
6
|
-
require "open-uri"
|
7
4
|
require "yaml"
|
8
5
|
require "digest/md5"
|
9
6
|
require "pathname"
|
10
7
|
|
11
8
|
class Thor::Runner < Thor #:nodoc: # rubocop:disable ClassLength
|
9
|
+
autoload :OpenURI, "open-uri"
|
10
|
+
|
12
11
|
map "-T" => :list, "-i" => :install, "-u" => :update, "-v" => :version
|
13
12
|
|
14
13
|
def self.banner(command, all = false, subcommand = false)
|
@@ -104,6 +103,7 @@ class Thor::Runner < Thor #:nodoc: # rubocop:disable ClassLength
|
|
104
103
|
if package == :file
|
105
104
|
File.open(destination, "w") { |f| f.puts contents }
|
106
105
|
else
|
106
|
+
require "fileutils"
|
107
107
|
FileUtils.cp_r(name, destination)
|
108
108
|
end
|
109
109
|
|
@@ -112,7 +112,7 @@ class Thor::Runner < Thor #:nodoc: # rubocop:disable ClassLength
|
|
112
112
|
|
113
113
|
desc "version", "Show Thor version"
|
114
114
|
def version
|
115
|
-
|
115
|
+
require_relative "version"
|
116
116
|
say "Thor #{Thor::VERSION}"
|
117
117
|
end
|
118
118
|
|
@@ -120,6 +120,7 @@ class Thor::Runner < Thor #:nodoc: # rubocop:disable ClassLength
|
|
120
120
|
def uninstall(name)
|
121
121
|
raise Error, "Can't find module '#{name}'" unless thor_yaml[name]
|
122
122
|
say "Uninstalling #{name}."
|
123
|
+
require "fileutils"
|
123
124
|
FileUtils.rm_rf(File.join(thor_root, (thor_yaml[name][:filename]).to_s))
|
124
125
|
|
125
126
|
thor_yaml.delete(name)
|
@@ -138,6 +139,7 @@ class Thor::Runner < Thor #:nodoc: # rubocop:disable ClassLength
|
|
138
139
|
self.options = options.merge("as" => name)
|
139
140
|
|
140
141
|
if File.directory? File.expand_path(name)
|
142
|
+
require "fileutils"
|
141
143
|
FileUtils.rm_rf(File.join(thor_root, old_filename))
|
142
144
|
|
143
145
|
thor_yaml.delete(old_filename)
|
@@ -194,6 +196,7 @@ private
|
|
194
196
|
yaml_file = File.join(thor_root, "thor.yml")
|
195
197
|
|
196
198
|
unless File.exist?(yaml_file)
|
199
|
+
require "fileutils"
|
197
200
|
FileUtils.mkdir_p(thor_root)
|
198
201
|
yaml_file = File.join(thor_root, "thor.yml")
|
199
202
|
FileUtils.touch(yaml_file)
|
data/lib/thor/shell/basic.rb
CHANGED
@@ -1,9 +1,8 @@
|
|
1
|
-
require "tempfile"
|
2
|
-
require "io/console" if RUBY_VERSION > "1.9.2"
|
3
|
-
|
4
1
|
class Thor
|
5
2
|
module Shell
|
6
3
|
class Basic
|
4
|
+
DEFAULT_TERMINAL_WIDTH = 80
|
5
|
+
|
7
6
|
attr_accessor :base
|
8
7
|
attr_reader :padding
|
9
8
|
|
@@ -48,6 +47,10 @@ class Thor
|
|
48
47
|
|
49
48
|
# Asks something to the user and receives a response.
|
50
49
|
#
|
50
|
+
# If a default value is specified it will be presented to the user
|
51
|
+
# and allows them to select that value with an empty response. This
|
52
|
+
# option is ignored when limited answers are supplied.
|
53
|
+
#
|
51
54
|
# If asked to limit the correct responses, you can pass in an
|
52
55
|
# array of acceptable answers. If one of those is not supplied,
|
53
56
|
# they will be shown a message stating that one of those answers
|
@@ -64,6 +67,8 @@ class Thor
|
|
64
67
|
# ==== Example
|
65
68
|
# ask("What is your name?")
|
66
69
|
#
|
70
|
+
# ask("What is the planet furthest from the sun?", :default => "Pluto")
|
71
|
+
#
|
67
72
|
# ask("What is your favorite Neopolitan flavor?", :limited_to => ["strawberry", "chocolate", "vanilla"])
|
68
73
|
#
|
69
74
|
# ask("What is your password?", :echo => false)
|
@@ -89,6 +94,8 @@ class Thor
|
|
89
94
|
# say("I know you knew that.")
|
90
95
|
#
|
91
96
|
def say(message = "", color = nil, force_new_line = (message.to_s !~ /( |\t)\Z/))
|
97
|
+
return if quiet?
|
98
|
+
|
92
99
|
buffer = prepare_message(message, *color)
|
93
100
|
buffer << "\n" if force_new_line && !message.to_s.end_with?("\n")
|
94
101
|
|
@@ -96,6 +103,23 @@ class Thor
|
|
96
103
|
stdout.flush
|
97
104
|
end
|
98
105
|
|
106
|
+
# Say (print) an error to the user. If the sentence ends with a whitespace
|
107
|
+
# or tab character, a new line is not appended (print + flush). Otherwise
|
108
|
+
# are passed straight to puts (behavior got from Highline).
|
109
|
+
#
|
110
|
+
# ==== Example
|
111
|
+
# say_error("error: something went wrong")
|
112
|
+
#
|
113
|
+
def say_error(message = "", color = nil, force_new_line = (message.to_s !~ /( |\t)\Z/))
|
114
|
+
return if quiet?
|
115
|
+
|
116
|
+
buffer = prepare_message(message, *color)
|
117
|
+
buffer << "\n" if force_new_line && !message.to_s.end_with?("\n")
|
118
|
+
|
119
|
+
stderr.print(buffer)
|
120
|
+
stderr.flush
|
121
|
+
end
|
122
|
+
|
99
123
|
# Say a status with the given color and appends the message. Since this
|
100
124
|
# method is used frequently by actions, it allows nil or false to be given
|
101
125
|
# in log_status, avoiding the message from being shown. If a Symbol is
|
@@ -104,13 +128,14 @@ class Thor
|
|
104
128
|
def say_status(status, message, log_status = true)
|
105
129
|
return if quiet? || log_status == false
|
106
130
|
spaces = " " * (padding + 1)
|
107
|
-
color = log_status.is_a?(Symbol) ? log_status : :green
|
108
|
-
|
109
131
|
status = status.to_s.rjust(12)
|
132
|
+
margin = " " * status.length + spaces
|
133
|
+
|
134
|
+
color = log_status.is_a?(Symbol) ? log_status : :green
|
110
135
|
status = set_color status, color, true if color
|
111
136
|
|
112
|
-
|
113
|
-
buffer
|
137
|
+
message = message.to_s.chomp.gsub(/(?<!\A)^/, margin)
|
138
|
+
buffer = "#{status}#{spaces}#{message}\n"
|
114
139
|
|
115
140
|
stdout.print(buffer)
|
116
141
|
stdout.flush
|
@@ -165,7 +190,7 @@ class Thor
|
|
165
190
|
colwidth = options[:colwidth]
|
166
191
|
options[:truncate] = terminal_width if options[:truncate] == true
|
167
192
|
|
168
|
-
formats << "%-#{colwidth + 2}s" if colwidth
|
193
|
+
formats << "%-#{colwidth + 2}s".dup if colwidth
|
169
194
|
start = colwidth ? 1 : 0
|
170
195
|
|
171
196
|
colcount = array.max { |a, b| a.size <=> b.size }.size
|
@@ -177,9 +202,9 @@ class Thor
|
|
177
202
|
maximas << maxima
|
178
203
|
formats << if index == colcount - 1
|
179
204
|
# Don't output 2 trailing spaces when printing the last column
|
180
|
-
"%-s"
|
205
|
+
"%-s".dup
|
181
206
|
else
|
182
|
-
"%-#{maxima + 2}s"
|
207
|
+
"%-#{maxima + 2}s".dup
|
183
208
|
end
|
184
209
|
end
|
185
210
|
|
@@ -187,7 +212,7 @@ class Thor
|
|
187
212
|
formats << "%s"
|
188
213
|
|
189
214
|
array.each do |row|
|
190
|
-
sentence = ""
|
215
|
+
sentence = "".dup
|
191
216
|
|
192
217
|
row.each_with_index do |column, index|
|
193
218
|
maxima = maximas[index]
|
@@ -225,8 +250,21 @@ class Thor
|
|
225
250
|
paras = message.split("\n\n")
|
226
251
|
|
227
252
|
paras.map! do |unwrapped|
|
228
|
-
|
229
|
-
|
253
|
+
words = unwrapped.split(" ")
|
254
|
+
counter = words.first.length
|
255
|
+
words.inject do |memo, word|
|
256
|
+
word = word.gsub(/\n\005/, "\n").gsub(/\005/, "\n")
|
257
|
+
counter = 0 if word.include? "\n"
|
258
|
+
if (counter + word.length + 1) < width
|
259
|
+
memo = "#{memo} #{word}"
|
260
|
+
counter += (word.length + 1)
|
261
|
+
else
|
262
|
+
memo = "#{memo}\n#{word}"
|
263
|
+
counter = word.length
|
264
|
+
end
|
265
|
+
memo
|
266
|
+
end
|
267
|
+
end.compact!
|
230
268
|
|
231
269
|
paras.each do |para|
|
232
270
|
para.split("\n").each do |line|
|
@@ -242,11 +280,11 @@ class Thor
|
|
242
280
|
#
|
243
281
|
# ==== Parameters
|
244
282
|
# destination<String>:: the destination file to solve conflicts
|
245
|
-
# block<Proc>:: an optional block that returns the value to be used in diff
|
283
|
+
# block<Proc>:: an optional block that returns the value to be used in diff and merge
|
246
284
|
#
|
247
285
|
def file_collision(destination)
|
248
286
|
return true if @always_force
|
249
|
-
options = block_given? ? "[
|
287
|
+
options = block_given? ? "[Ynaqdhm]" : "[Ynaqh]"
|
250
288
|
|
251
289
|
loop do
|
252
290
|
answer = ask(
|
@@ -255,6 +293,9 @@ class Thor
|
|
255
293
|
)
|
256
294
|
|
257
295
|
case answer
|
296
|
+
when nil
|
297
|
+
say ""
|
298
|
+
return true
|
258
299
|
when is?(:yes), is?(:force), ""
|
259
300
|
return true
|
260
301
|
when is?(:no), is?(:skip)
|
@@ -267,6 +308,13 @@ class Thor
|
|
267
308
|
when is?(:diff)
|
268
309
|
show_diff(destination, yield) if block_given?
|
269
310
|
say "Retrying..."
|
311
|
+
when is?(:merge)
|
312
|
+
if block_given? && !merge_tool.empty?
|
313
|
+
merge(destination, yield)
|
314
|
+
return nil
|
315
|
+
end
|
316
|
+
|
317
|
+
say "Please specify merge tool to `THOR_MERGE` env."
|
270
318
|
else
|
271
319
|
say file_collision_help
|
272
320
|
end
|
@@ -279,11 +327,11 @@ class Thor
|
|
279
327
|
result = if ENV["THOR_COLUMNS"]
|
280
328
|
ENV["THOR_COLUMNS"].to_i
|
281
329
|
else
|
282
|
-
unix? ? dynamic_width :
|
330
|
+
unix? ? dynamic_width : DEFAULT_TERMINAL_WIDTH
|
283
331
|
end
|
284
|
-
result < 10 ?
|
332
|
+
result < 10 ? DEFAULT_TERMINAL_WIDTH : result
|
285
333
|
rescue
|
286
|
-
|
334
|
+
DEFAULT_TERMINAL_WIDTH
|
287
335
|
end
|
288
336
|
|
289
337
|
# Called if something goes wrong during the execution. This is used by Thor
|
@@ -344,12 +392,14 @@ class Thor
|
|
344
392
|
q - quit, abort
|
345
393
|
d - diff, show the differences between the old and the new
|
346
394
|
h - help, show this help
|
395
|
+
m - merge, run merge tool
|
347
396
|
HELP
|
348
397
|
end
|
349
398
|
|
350
399
|
def show_diff(destination, content) #:nodoc:
|
351
400
|
diff_cmd = ENV["THOR_DIFF"] || ENV["RAILS_DIFF"] || "diff -u"
|
352
401
|
|
402
|
+
require "tempfile"
|
353
403
|
Tempfile.open(File.basename(destination), File.dirname(destination)) do |temp|
|
354
404
|
temp.write content
|
355
405
|
temp.rewind
|
@@ -411,7 +461,7 @@ class Thor
|
|
411
461
|
|
412
462
|
return unless result
|
413
463
|
|
414
|
-
result.strip
|
464
|
+
result = result.strip
|
415
465
|
|
416
466
|
if default && result == ""
|
417
467
|
default
|
@@ -422,15 +472,41 @@ class Thor
|
|
422
472
|
|
423
473
|
def ask_filtered(statement, color, options)
|
424
474
|
answer_set = options[:limited_to]
|
475
|
+
case_insensitive = options.fetch(:case_insensitive, false)
|
425
476
|
correct_answer = nil
|
426
477
|
until correct_answer
|
427
478
|
answers = answer_set.join(", ")
|
428
479
|
answer = ask_simply("#{statement} [#{answers}]", color, options)
|
429
|
-
correct_answer = answer_set
|
480
|
+
correct_answer = answer_match(answer_set, answer, case_insensitive)
|
430
481
|
say("Your response must be one of: [#{answers}]. Please try again.") unless correct_answer
|
431
482
|
end
|
432
483
|
correct_answer
|
433
484
|
end
|
485
|
+
|
486
|
+
def answer_match(possibilities, answer, case_insensitive)
|
487
|
+
if case_insensitive
|
488
|
+
possibilities.detect{ |possibility| possibility.downcase == answer.downcase }
|
489
|
+
else
|
490
|
+
possibilities.detect{ |possibility| possibility == answer }
|
491
|
+
end
|
492
|
+
end
|
493
|
+
|
494
|
+
def merge(destination, content) #:nodoc:
|
495
|
+
require "tempfile"
|
496
|
+
Tempfile.open([File.basename(destination), File.extname(destination)], File.dirname(destination)) do |temp|
|
497
|
+
temp.write content
|
498
|
+
temp.rewind
|
499
|
+
system %(#{merge_tool} "#{temp.path}" "#{destination}")
|
500
|
+
end
|
501
|
+
end
|
502
|
+
|
503
|
+
def merge_tool #:nodoc:
|
504
|
+
@merge_tool ||= ENV["THOR_MERGE"] || git_merge_tool
|
505
|
+
end
|
506
|
+
|
507
|
+
def git_merge_tool #:nodoc:
|
508
|
+
`git config merge.tool`.rstrip rescue ""
|
509
|
+
end
|
434
510
|
end
|
435
511
|
end
|
436
512
|
end
|
data/lib/thor/shell/color.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
require_relative "basic"
|
2
2
|
|
3
3
|
class Thor
|
4
4
|
module Shell
|
@@ -97,7 +97,15 @@ class Thor
|
|
97
97
|
protected
|
98
98
|
|
99
99
|
def can_display_colors?
|
100
|
-
|
100
|
+
are_colors_supported? && !are_colors_disabled?
|
101
|
+
end
|
102
|
+
|
103
|
+
def are_colors_supported?
|
104
|
+
stdout.tty? && ENV["TERM"] != "dumb"
|
105
|
+
end
|
106
|
+
|
107
|
+
def are_colors_disabled?
|
108
|
+
!ENV['NO_COLOR'].nil?
|
101
109
|
end
|
102
110
|
|
103
111
|
# Overwrite show_diff to show diff with colors if Diff::LCS is
|
data/lib/thor/shell/html.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
require_relative "basic"
|
2
2
|
|
3
3
|
class Thor
|
4
4
|
module Shell
|
@@ -51,13 +51,13 @@ class Thor
|
|
51
51
|
def set_color(string, *colors)
|
52
52
|
if colors.all? { |color| color.is_a?(Symbol) || color.is_a?(String) }
|
53
53
|
html_colors = colors.map { |color| lookup_color(color) }
|
54
|
-
"<span style=\"#{html_colors.join('; ')};\">#{string}</span>"
|
54
|
+
"<span style=\"#{html_colors.join('; ')};\">#{Thor::Util.escape_html(string)}</span>"
|
55
55
|
else
|
56
56
|
color, bold = colors
|
57
57
|
html_color = self.class.const_get(color.to_s.upcase) if color.is_a?(Symbol)
|
58
58
|
styles = [html_color]
|
59
59
|
styles << BOLD if bold
|
60
|
-
"<span style=\"#{styles.join('; ')};\">#{string}</span>"
|
60
|
+
"<span style=\"#{styles.join('; ')};\">#{Thor::Util.escape_html(string)}</span>"
|
61
61
|
end
|
62
62
|
end
|
63
63
|
|
data/lib/thor/shell.rb
CHANGED
@@ -21,12 +21,12 @@ class Thor
|
|
21
21
|
end
|
22
22
|
|
23
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]
|
24
|
+
SHELL_DELEGATED_METHODS = [:ask, :error, :set_color, :yes?, :no?, :say, :say_error, :say_status, :print_in_columns, :print_table, :print_wrapped, :file_collision, :terminal_width]
|
25
25
|
attr_writer :shell
|
26
26
|
|
27
|
-
autoload :Basic, "
|
28
|
-
autoload :Color, "
|
29
|
-
autoload :HTML, "
|
27
|
+
autoload :Basic, File.expand_path("shell/basic", __dir__)
|
28
|
+
autoload :Color, File.expand_path("shell/color", __dir__)
|
29
|
+
autoload :HTML, File.expand_path("shell/html", __dir__)
|
30
30
|
|
31
31
|
# Add shell to initialize config values.
|
32
32
|
#
|
@@ -55,7 +55,7 @@ class Thor
|
|
55
55
|
|
56
56
|
# Common methods that are delegated to the shell.
|
57
57
|
SHELL_DELEGATED_METHODS.each do |method|
|
58
|
-
module_eval <<-METHOD, __FILE__, __LINE__
|
58
|
+
module_eval <<-METHOD, __FILE__, __LINE__ + 1
|
59
59
|
def #{method}(*args,&block)
|
60
60
|
shell.#{method}(*args,&block)
|
61
61
|
end
|
data/lib/thor/util.rb
CHANGED
@@ -27,7 +27,7 @@ class Thor
|
|
27
27
|
end
|
28
28
|
|
29
29
|
# Receives a constant and converts it to a Thor namespace. Since Thor
|
30
|
-
# commands can be added to a sandbox, this method is also
|
30
|
+
# commands can be added to a sandbox, this method is also responsible for
|
31
31
|
# removing the sandbox namespace.
|
32
32
|
#
|
33
33
|
# This method should not be used in general because it's used to deal with
|
@@ -211,7 +211,7 @@ class Thor
|
|
211
211
|
#
|
212
212
|
def globs_for(path)
|
213
213
|
path = escape_globs(path)
|
214
|
-
["#{path}/Thorfile", "#{path}/*.thor", "#{path}/tasks/*.thor", "#{path}/lib/tasks
|
214
|
+
["#{path}/Thorfile", "#{path}/*.thor", "#{path}/tasks/*.thor", "#{path}/lib/tasks/**/*.thor"]
|
215
215
|
end
|
216
216
|
|
217
217
|
# Return the path to the ruby interpreter taking into account multiple
|
@@ -263,6 +263,22 @@ class Thor
|
|
263
263
|
def escape_globs(path)
|
264
264
|
path.to_s.gsub(/[*?{}\[\]]/, '\\\\\\&')
|
265
265
|
end
|
266
|
+
|
267
|
+
# Returns a string that has had any HTML characters escaped.
|
268
|
+
#
|
269
|
+
# ==== Examples
|
270
|
+
#
|
271
|
+
# Thor::Util.escape_html('<div>') # => "<div>"
|
272
|
+
#
|
273
|
+
# ==== Parameters
|
274
|
+
# String
|
275
|
+
#
|
276
|
+
# ==== Returns
|
277
|
+
# String
|
278
|
+
#
|
279
|
+
def escape_html(string)
|
280
|
+
CGI.escapeHTML(string)
|
281
|
+
end
|
266
282
|
end
|
267
283
|
end
|
268
284
|
end
|
data/lib/thor/version.rb
CHANGED