thor 0.20.3 → 1.2.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +1 -7
- data/lib/thor/actions/create_file.rb +2 -2
- data/lib/thor/actions/create_link.rb +3 -2
- data/lib/thor/actions/directory.rb +7 -17
- data/lib/thor/actions/file_manipulation.rb +14 -12
- data/lib/thor/actions/inject_into_file.rb +20 -10
- data/lib/thor/actions.rb +27 -17
- data/lib/thor/base.rb +63 -43
- data/lib/thor/command.rb +21 -14
- data/lib/thor/core_ext/hash_with_indifferent_access.rb +6 -0
- data/lib/thor/error.rb +22 -25
- data/lib/thor/group.rb +2 -2
- data/lib/thor/invocation.rb +1 -0
- data/lib/thor/line_editor/basic.rb +1 -1
- 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 +8 -4
- data/lib/thor/parser/option.rb +27 -10
- data/lib/thor/parser/options.rb +36 -7
- data/lib/thor/parser.rb +4 -4
- data/lib/thor/rake_compat.rb +3 -2
- data/lib/thor/runner.rb +35 -24
- data/lib/thor/shell/basic.rb +39 -9
- data/lib/thor/shell/color.rb +10 -2
- data/lib/thor/shell/html.rb +3 -3
- data/lib/thor/shell.rb +4 -4
- data/lib/thor/util.rb +21 -5
- data/lib/thor/version.rb +1 -1
- data/lib/thor.rb +17 -10
- data/thor.gemspec +10 -2
- metadata +20 -11
- data/CHANGELOG.md +0 -204
- data/lib/thor/core_ext/io_binary_read.rb +0 -12
- data/lib/thor/core_ext/ordered_hash.rb +0 -129
data/lib/thor/error.rb
CHANGED
@@ -1,22 +1,26 @@
|
|
1
1
|
class Thor
|
2
|
-
Correctable =
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
2
|
+
Correctable = if defined?(DidYouMean::SpellChecker) && defined?(DidYouMean::Correctable) # rubocop:disable Naming/ConstantName
|
3
|
+
# In order to support versions of Ruby that don't have keyword
|
4
|
+
# arguments, we need our own spell checker class that doesn't take key
|
5
|
+
# words. Even though this code wouldn't be hit because of the check
|
6
|
+
# above, it's still necessary because the interpreter would otherwise be
|
7
|
+
# unable to parse the file.
|
8
|
+
class NoKwargSpellChecker < DidYouMean::SpellChecker # :nodoc:
|
9
|
+
def initialize(dictionary)
|
10
|
+
@dictionary = dictionary
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
Module.new do
|
15
|
+
def to_s
|
16
|
+
super + DidYouMean.formatter.message_for(corrections)
|
17
|
+
end
|
18
|
+
|
19
|
+
def corrections
|
20
|
+
@corrections ||= self.class.const_get(:SpellChecker).new(self).corrections
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
20
24
|
|
21
25
|
# Thor::Error is raised when it's caused by wrong usage of thor classes. Those
|
22
26
|
# errors have their backtrace suppressed and are nicely shown to the user.
|
@@ -104,11 +108,4 @@ class Thor
|
|
104
108
|
|
105
109
|
class MalformattedArgumentError < InvocationError
|
106
110
|
end
|
107
|
-
|
108
|
-
if Correctable
|
109
|
-
DidYouMean::SPELL_CHECKERS.merge!(
|
110
|
-
'Thor::UndefinedCommandError' => UndefinedCommandError::SpellChecker,
|
111
|
-
'Thor::UnknownArgumentError' => UnknownArgumentError::SpellChecker
|
112
|
-
)
|
113
|
-
end
|
114
111
|
end
|
data/lib/thor/group.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
require_relative "base"
|
2
2
|
|
3
3
|
# Thor has a special class called Thor::Group. The main difference to Thor class
|
4
4
|
# is that it invokes all commands at once. It also include some methods that allows
|
@@ -169,7 +169,7 @@ class Thor::Group
|
|
169
169
|
# options are added to group_options hash. Options that already exists
|
170
170
|
# in base_options are not added twice.
|
171
171
|
#
|
172
|
-
def get_options_from_invocations(group_options, base_options) #:nodoc:
|
172
|
+
def get_options_from_invocations(group_options, base_options) #:nodoc:
|
173
173
|
invocations.each do |name, from_option|
|
174
174
|
value = if from_option
|
175
175
|
option = class_options[name]
|
data/lib/thor/invocation.rb
CHANGED
@@ -1,19 +1,19 @@
|
|
1
|
-
begin
|
2
|
-
require "readline"
|
3
|
-
rescue LoadError
|
4
|
-
end
|
5
|
-
|
6
1
|
class Thor
|
7
2
|
module LineEditor
|
8
3
|
class Readline < Basic
|
9
4
|
def self.available?
|
5
|
+
begin
|
6
|
+
require "readline"
|
7
|
+
rescue LoadError
|
8
|
+
end
|
9
|
+
|
10
10
|
Object.const_defined?(:Readline)
|
11
11
|
end
|
12
12
|
|
13
13
|
def readline
|
14
14
|
if echo?
|
15
15
|
::Readline.completion_append_character = nil
|
16
|
-
#
|
16
|
+
# rb-readline does not allow Readline.completion_proc= to receive nil.
|
17
17
|
if complete = completion_proc
|
18
18
|
::Readline.completion_proc = complete
|
19
19
|
end
|
data/lib/thor/line_editor.rb
CHANGED
@@ -0,0 +1,29 @@
|
|
1
|
+
class Thor
|
2
|
+
class NestedContext
|
3
|
+
def initialize
|
4
|
+
@depth = 0
|
5
|
+
end
|
6
|
+
|
7
|
+
def enter
|
8
|
+
push
|
9
|
+
|
10
|
+
yield
|
11
|
+
ensure
|
12
|
+
pop
|
13
|
+
end
|
14
|
+
|
15
|
+
def entered?
|
16
|
+
@depth > 0
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def push
|
22
|
+
@depth += 1
|
23
|
+
end
|
24
|
+
|
25
|
+
def pop
|
26
|
+
@depth -= 1
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
class Thor
|
2
|
-
class Arguments #:nodoc:
|
2
|
+
class Arguments #:nodoc:
|
3
3
|
NUMERIC = /[-+]?(\d*\.\d+|\d+)/
|
4
4
|
|
5
5
|
# Receives an array of args and returns two arrays, one with arguments
|
@@ -9,7 +9,7 @@ class Thor
|
|
9
9
|
arguments = []
|
10
10
|
|
11
11
|
args.each do |item|
|
12
|
-
break if item =~ /^-/
|
12
|
+
break if item.is_a?(String) && item =~ /^-/
|
13
13
|
arguments << item
|
14
14
|
end
|
15
15
|
|
@@ -30,7 +30,11 @@ class Thor
|
|
30
30
|
|
31
31
|
arguments.each do |argument|
|
32
32
|
if !argument.default.nil?
|
33
|
-
|
33
|
+
begin
|
34
|
+
@assigns[argument.human_name] = argument.default.dup
|
35
|
+
rescue TypeError # Compatibility shim for un-dup-able Fixnum in Ruby < 2.4
|
36
|
+
@assigns[argument.human_name] = argument.default
|
37
|
+
end
|
34
38
|
elsif argument.required?
|
35
39
|
@non_assigned_required << argument
|
36
40
|
end
|
@@ -82,7 +86,7 @@ class Thor
|
|
82
86
|
end
|
83
87
|
|
84
88
|
def current_is_value?
|
85
|
-
peek && peek.to_s !~
|
89
|
+
peek && peek.to_s !~ /^-{1,2}\S+/
|
86
90
|
end
|
87
91
|
|
88
92
|
# Runs through the argument array getting strings that contains ":" and
|
data/lib/thor/parser/option.rb
CHANGED
@@ -1,17 +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
8
|
@check_default_type = options[:check_default_type]
|
9
9
|
options[:required] = false unless options.key?(:required)
|
10
|
+
@repeatable = options.fetch(:repeatable, false)
|
10
11
|
super
|
11
|
-
@lazy_default
|
12
|
-
@group
|
13
|
-
@aliases
|
14
|
-
@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]
|
15
16
|
end
|
16
17
|
|
17
18
|
# This parse quick options given as method_options. It makes several
|
@@ -57,7 +58,7 @@ class Thor
|
|
57
58
|
default = nil
|
58
59
|
if VALID_TYPES.include?(value)
|
59
60
|
value
|
60
|
-
elsif required = (value == :required) # rubocop:disable AssignmentInCondition
|
61
|
+
elsif required = (value == :required) # rubocop:disable Lint/AssignmentInCondition
|
61
62
|
:string
|
62
63
|
end
|
63
64
|
when TrueClass, FalseClass
|
@@ -92,10 +93,14 @@ class Thor
|
|
92
93
|
sample << ", [#{dasherize('no-' + human_name)}]" unless (name == "force") || name.start_with?("no-")
|
93
94
|
end
|
94
95
|
|
96
|
+
aliases_for_usage.ljust(padding) + sample
|
97
|
+
end
|
98
|
+
|
99
|
+
def aliases_for_usage
|
95
100
|
if aliases.empty?
|
96
|
-
|
101
|
+
""
|
97
102
|
else
|
98
|
-
"#{aliases.join(', ')},
|
103
|
+
"#{aliases.join(', ')}, "
|
99
104
|
end
|
100
105
|
end
|
101
106
|
|
@@ -111,7 +116,7 @@ class Thor
|
|
111
116
|
|
112
117
|
def validate!
|
113
118
|
raise ArgumentError, "An option cannot be boolean and required." if boolean? && required?
|
114
|
-
validate_default_type!
|
119
|
+
validate_default_type!
|
115
120
|
end
|
116
121
|
|
117
122
|
def validate_default_type!
|
@@ -128,7 +133,19 @@ class Thor
|
|
128
133
|
@default.class.name.downcase.to_sym
|
129
134
|
end
|
130
135
|
|
131
|
-
|
136
|
+
expected_type = (@repeatable && @type != :hash) ? :array : @type
|
137
|
+
|
138
|
+
if default_type != expected_type
|
139
|
+
err = "Expected #{expected_type} default value for '#{switch_name}'; got #{@default.inspect} (#{default_type})"
|
140
|
+
|
141
|
+
if @check_default_type
|
142
|
+
raise ArgumentError, err
|
143
|
+
elsif @check_default_type == nil
|
144
|
+
Thor.deprecation_warning "#{err}.\n" +
|
145
|
+
'This will be rejected in the future unless you explicitly pass the options `check_default_type: false`' +
|
146
|
+
' or call `allow_incompatible_default_type!` in your code'
|
147
|
+
end
|
148
|
+
end
|
132
149
|
end
|
133
150
|
|
134
151
|
def dasherized?
|
data/lib/thor/parser/options.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
class Thor
|
2
|
-
class Options < Arguments #:nodoc:
|
2
|
+
class Options < Arguments #:nodoc:
|
3
3
|
LONG_RE = /^(--\w+(?:-\w+)*)$/
|
4
4
|
SHORT_RE = /^(-[a-z])$/i
|
5
5
|
EQ_RE = /^(--\w+(?:-\w+)*|-[a-z])=(.*)$/i
|
@@ -45,6 +45,7 @@ class Thor
|
|
45
45
|
@switches = {}
|
46
46
|
@extra = []
|
47
47
|
@stopped_parsing_after_extra_index = nil
|
48
|
+
@is_treated_as_value = false
|
48
49
|
|
49
50
|
options.each do |option|
|
50
51
|
@switches[option.switch_name] = option
|
@@ -74,8 +75,19 @@ class Thor
|
|
74
75
|
end
|
75
76
|
end
|
76
77
|
|
77
|
-
def
|
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
|
+
|
88
|
+
def parse(args) # rubocop:disable Metrics/MethodLength
|
78
89
|
@pile = args.dup
|
90
|
+
@is_treated_as_value = false
|
79
91
|
@parsing_options = true
|
80
92
|
|
81
93
|
while peek
|
@@ -88,7 +100,10 @@ class Thor
|
|
88
100
|
when SHORT_SQ_RE
|
89
101
|
unshift($1.split("").map { |f| "-#{f}" })
|
90
102
|
next
|
91
|
-
when EQ_RE
|
103
|
+
when EQ_RE
|
104
|
+
unshift($2, :is_value => true)
|
105
|
+
switch = $1
|
106
|
+
when SHORT_NUM
|
92
107
|
unshift($2)
|
93
108
|
switch = $1
|
94
109
|
when LONG_RE, SHORT_RE
|
@@ -97,7 +112,8 @@ class Thor
|
|
97
112
|
|
98
113
|
switch = normalize_switch(switch)
|
99
114
|
option = switch_option(switch)
|
100
|
-
|
115
|
+
result = parse_peek(switch, option)
|
116
|
+
assign_result!(option, result)
|
101
117
|
elsif @stop_on_unknown
|
102
118
|
@parsing_options = false
|
103
119
|
@extra << shifted
|
@@ -132,11 +148,22 @@ class Thor
|
|
132
148
|
|
133
149
|
protected
|
134
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
|
+
|
135
161
|
# Check if the current value in peek is a registered switch.
|
136
162
|
#
|
137
163
|
# Two booleans are returned. The first is true if the current value
|
138
164
|
# starts with a hyphen; the second is true if it is a registered switch.
|
139
165
|
def current_is_switch?
|
166
|
+
return [false, false] if @is_treated_as_value
|
140
167
|
case peek
|
141
168
|
when LONG_RE, SHORT_RE, EQ_RE, SHORT_NUM
|
142
169
|
[true, switch?($1)]
|
@@ -148,6 +175,7 @@ class Thor
|
|
148
175
|
end
|
149
176
|
|
150
177
|
def current_is_switch_formatted?
|
178
|
+
return false if @is_treated_as_value
|
151
179
|
case peek
|
152
180
|
when LONG_RE, SHORT_RE, EQ_RE, SHORT_NUM, SHORT_SQ_RE
|
153
181
|
true
|
@@ -157,15 +185,16 @@ class Thor
|
|
157
185
|
end
|
158
186
|
|
159
187
|
def current_is_value?
|
188
|
+
return true if @is_treated_as_value
|
160
189
|
peek && (!parsing_options? || super)
|
161
190
|
end
|
162
191
|
|
163
192
|
def switch?(arg)
|
164
|
-
switch_option(normalize_switch(arg))
|
193
|
+
!switch_option(normalize_switch(arg)).nil?
|
165
194
|
end
|
166
195
|
|
167
196
|
def switch_option(arg)
|
168
|
-
if match = no_or_skip?(arg) # rubocop:disable AssignmentInCondition
|
197
|
+
if match = no_or_skip?(arg) # rubocop:disable Lint/AssignmentInCondition
|
169
198
|
@switches[arg] || @switches["--#{match}"]
|
170
199
|
else
|
171
200
|
@switches[arg]
|
@@ -194,7 +223,7 @@ class Thor
|
|
194
223
|
shift
|
195
224
|
false
|
196
225
|
else
|
197
|
-
!no_or_skip?(switch)
|
226
|
+
@switches.key?(switch) || !no_or_skip?(switch)
|
198
227
|
end
|
199
228
|
else
|
200
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)
|
@@ -40,7 +41,7 @@ instance_eval do
|
|
40
41
|
def task(*)
|
41
42
|
task = super
|
42
43
|
|
43
|
-
if klass = Thor::RakeCompat.rake_classes.last # rubocop:disable AssignmentInCondition
|
44
|
+
if klass = Thor::RakeCompat.rake_classes.last # rubocop:disable Lint/AssignmentInCondition
|
44
45
|
non_namespaced_name = task.name.split(":").last
|
45
46
|
|
46
47
|
description = non_namespaced_name
|
@@ -58,7 +59,7 @@ instance_eval do
|
|
58
59
|
end
|
59
60
|
|
60
61
|
def namespace(name)
|
61
|
-
if klass = Thor::RakeCompat.rake_classes.last # rubocop:disable AssignmentInCondition
|
62
|
+
if klass = Thor::RakeCompat.rake_classes.last # rubocop:disable Lint/AssignmentInCondition
|
62
63
|
const_name = Thor::Util.camel_case(name.to_s).to_sym
|
63
64
|
klass.const_set(const_name, Class.new(Thor))
|
64
65
|
new_klass = klass.const_get(const_name)
|
data/lib/thor/runner.rb
CHANGED
@@ -1,12 +1,11 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require "thor/core_ext/io_binary_read"
|
1
|
+
require_relative "../thor"
|
2
|
+
require_relative "group"
|
4
3
|
|
5
4
|
require "yaml"
|
6
|
-
require "digest/
|
5
|
+
require "digest/sha2"
|
7
6
|
require "pathname"
|
8
7
|
|
9
|
-
class Thor::Runner < Thor #:nodoc:
|
8
|
+
class Thor::Runner < Thor #:nodoc:
|
10
9
|
map "-T" => :list, "-i" => :install, "-u" => :update, "-v" => :version
|
11
10
|
|
12
11
|
def self.banner(command, all = false, subcommand = false)
|
@@ -44,25 +43,37 @@ class Thor::Runner < Thor #:nodoc: # rubocop:disable ClassLength
|
|
44
43
|
|
45
44
|
desc "install NAME", "Install an optionally named Thor file into your system commands"
|
46
45
|
method_options :as => :string, :relative => :boolean, :force => :boolean
|
47
|
-
def install(name) # rubocop:disable MethodLength
|
46
|
+
def install(name) # rubocop:disable Metrics/MethodLength
|
48
47
|
initialize_thorfiles
|
49
48
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
49
|
+
is_uri = name =~ %r{^https?\://}
|
50
|
+
|
51
|
+
if is_uri
|
52
|
+
base = name
|
53
|
+
package = :file
|
54
|
+
require "open-uri"
|
55
|
+
begin
|
56
|
+
contents = URI.send(:open, name, &:read) # Using `send` for Ruby 2.4- support
|
57
|
+
rescue OpenURI::HTTPError
|
58
|
+
raise Error, "Error opening URI '#{name}'"
|
59
|
+
end
|
60
|
+
else
|
61
|
+
# If a directory name is provided as the argument, look for a 'main.thor'
|
62
|
+
# command in said directory.
|
63
|
+
begin
|
64
|
+
if File.directory?(File.expand_path(name))
|
65
|
+
base = File.join(name, "main.thor")
|
66
|
+
package = :directory
|
67
|
+
contents = File.open(base, &:read)
|
68
|
+
else
|
69
|
+
base = name
|
70
|
+
package = :file
|
71
|
+
require "open-uri"
|
72
|
+
contents = URI.send(:open, name, &:read) # for ruby 2.1-2.4
|
73
|
+
end
|
74
|
+
rescue Errno::ENOENT
|
75
|
+
raise Error, "Error opening file '#{name}'"
|
61
76
|
end
|
62
|
-
rescue OpenURI::HTTPError
|
63
|
-
raise Error, "Error opening URI '#{name}'"
|
64
|
-
rescue Errno::ENOENT
|
65
|
-
raise Error, "Error opening file '#{name}'"
|
66
77
|
end
|
67
78
|
|
68
79
|
say "Your Thorfile contains:"
|
@@ -83,14 +94,14 @@ class Thor::Runner < Thor #:nodoc: # rubocop:disable ClassLength
|
|
83
94
|
as = basename if as.empty?
|
84
95
|
end
|
85
96
|
|
86
|
-
location = if options[:relative] ||
|
97
|
+
location = if options[:relative] || is_uri
|
87
98
|
name
|
88
99
|
else
|
89
100
|
File.expand_path(name)
|
90
101
|
end
|
91
102
|
|
92
103
|
thor_yaml[as] = {
|
93
|
-
:filename => Digest::
|
104
|
+
:filename => Digest::SHA256.hexdigest(name + as),
|
94
105
|
:location => location,
|
95
106
|
:namespaces => Thor::Util.namespaces_in_content(contents, base)
|
96
107
|
}
|
@@ -111,7 +122,7 @@ class Thor::Runner < Thor #:nodoc: # rubocop:disable ClassLength
|
|
111
122
|
|
112
123
|
desc "version", "Show Thor version"
|
113
124
|
def version
|
114
|
-
|
125
|
+
require_relative "version"
|
115
126
|
say "Thor #{Thor::VERSION}"
|
116
127
|
end
|
117
128
|
|
data/lib/thor/shell/basic.rb
CHANGED
@@ -94,6 +94,8 @@ class Thor
|
|
94
94
|
# say("I know you knew that.")
|
95
95
|
#
|
96
96
|
def say(message = "", color = nil, force_new_line = (message.to_s !~ /( |\t)\Z/))
|
97
|
+
return if quiet?
|
98
|
+
|
97
99
|
buffer = prepare_message(message, *color)
|
98
100
|
buffer << "\n" if force_new_line && !message.to_s.end_with?("\n")
|
99
101
|
|
@@ -101,6 +103,23 @@ class Thor
|
|
101
103
|
stdout.flush
|
102
104
|
end
|
103
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
|
+
|
104
123
|
# Say a status with the given color and appends the message. Since this
|
105
124
|
# method is used frequently by actions, it allows nil or false to be given
|
106
125
|
# in log_status, avoiding the message from being shown. If a Symbol is
|
@@ -109,13 +128,14 @@ class Thor
|
|
109
128
|
def say_status(status, message, log_status = true)
|
110
129
|
return if quiet? || log_status == false
|
111
130
|
spaces = " " * (padding + 1)
|
112
|
-
color = log_status.is_a?(Symbol) ? log_status : :green
|
113
|
-
|
114
131
|
status = status.to_s.rjust(12)
|
132
|
+
margin = " " * status.length + spaces
|
133
|
+
|
134
|
+
color = log_status.is_a?(Symbol) ? log_status : :green
|
115
135
|
status = set_color status, color, true if color
|
116
136
|
|
117
|
-
|
118
|
-
buffer = "#{
|
137
|
+
message = message.to_s.chomp.gsub(/(?<!\A)^/, margin)
|
138
|
+
buffer = "#{status}#{spaces}#{message}\n"
|
119
139
|
|
120
140
|
stdout.print(buffer)
|
121
141
|
stdout.flush
|
@@ -162,7 +182,7 @@ class Thor
|
|
162
182
|
# indent<Integer>:: Indent the first column by indent value.
|
163
183
|
# colwidth<Integer>:: Force the first column to colwidth spaces wide.
|
164
184
|
#
|
165
|
-
def print_table(array, options = {}) # rubocop:disable MethodLength
|
185
|
+
def print_table(array, options = {}) # rubocop:disable Metrics/MethodLength
|
166
186
|
return if array.empty?
|
167
187
|
|
168
188
|
formats = []
|
@@ -230,8 +250,9 @@ class Thor
|
|
230
250
|
paras = message.split("\n\n")
|
231
251
|
|
232
252
|
paras.map! do |unwrapped|
|
233
|
-
|
234
|
-
|
253
|
+
words = unwrapped.split(" ")
|
254
|
+
counter = words.first.length
|
255
|
+
words.inject do |memo, word|
|
235
256
|
word = word.gsub(/\n\005/, "\n").gsub(/\005/, "\n")
|
236
257
|
counter = 0 if word.include? "\n"
|
237
258
|
if (counter + word.length + 1) < width
|
@@ -404,7 +425,7 @@ class Thor
|
|
404
425
|
end
|
405
426
|
|
406
427
|
def unix?
|
407
|
-
RUBY_PLATFORM =~ /(aix|darwin|linux|(net|free|open)bsd|cygwin|solaris
|
428
|
+
RUBY_PLATFORM =~ /(aix|darwin|linux|(net|free|open)bsd|cygwin|solaris)/i
|
408
429
|
end
|
409
430
|
|
410
431
|
def truncate(string, width)
|
@@ -451,16 +472,25 @@ class Thor
|
|
451
472
|
|
452
473
|
def ask_filtered(statement, color, options)
|
453
474
|
answer_set = options[:limited_to]
|
475
|
+
case_insensitive = options.fetch(:case_insensitive, false)
|
454
476
|
correct_answer = nil
|
455
477
|
until correct_answer
|
456
478
|
answers = answer_set.join(", ")
|
457
479
|
answer = ask_simply("#{statement} [#{answers}]", color, options)
|
458
|
-
correct_answer = answer_set
|
480
|
+
correct_answer = answer_match(answer_set, answer, case_insensitive)
|
459
481
|
say("Your response must be one of: [#{answers}]. Please try again.") unless correct_answer
|
460
482
|
end
|
461
483
|
correct_answer
|
462
484
|
end
|
463
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
|
+
|
464
494
|
def merge(destination, content) #:nodoc:
|
465
495
|
require "tempfile"
|
466
496
|
Tempfile.open([File.basename(destination), File.extname(destination)], File.dirname(destination)) do |temp|
|
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? && !ENV['NO_COLOR'].empty?
|
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
|
|