thor 0.19.4 → 1.2.1
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 +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