thor 0.20.3 → 1.2.2

Sign up to get free protection for your applications and to get access to all the features.
data/lib/thor/error.rb CHANGED
@@ -1,22 +1,26 @@
1
1
  class Thor
2
- Correctable =
3
- begin
4
- require 'did_you_mean'
5
-
6
- # In order to support versions of Ruby that don't have keyword
7
- # arguments, we need our own spell checker class that doesn't take key
8
- # words. Even though this code wouldn't be hit because of the check
9
- # above, it's still necessary because the interpreter would otherwise be
10
- # unable to parse the file.
11
- class NoKwargSpellChecker < DidYouMean::SpellChecker # :nodoc:
12
- def initialize(dictionary)
13
- @dictionary = dictionary
14
- end
15
- end
16
-
17
- DidYouMean::Correctable
18
- rescue LoadError, NameError
19
- end
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
- require "thor/base"
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: # rubocop:disable MethodLength
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]
@@ -1,6 +1,7 @@
1
1
  class Thor
2
2
  module Invocation
3
3
  def self.included(base) #:nodoc:
4
+ super(base)
4
5
  base.extend ClassMethods
5
6
  end
6
7
 
@@ -24,7 +24,7 @@ class Thor
24
24
  $stdin.gets
25
25
  else
26
26
  # Lazy-load io/console since it is gem-ified as of 2.3
27
- require "io/console" if RUBY_VERSION > "1.9.2"
27
+ require "io/console"
28
28
  $stdin.noecho(&:gets)
29
29
  end
30
30
  end
@@ -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
- # Ruby 1.8.7 does not allow Readline.completion_proc= to receive nil.
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
@@ -1,5 +1,5 @@
1
- require "thor/line_editor/basic"
2
- require "thor/line_editor/readline"
1
+ require_relative "line_editor/basic"
2
+ require_relative "line_editor/readline"
3
3
 
4
4
  class Thor
5
5
  module LineEditor
@@ -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: # rubocop:disable ClassLength
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
- @assigns[argument.human_name] = argument.default
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
@@ -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 = options[:lazy_default]
12
- @group = options[:group].to_s.capitalize if options[:group]
13
- @aliases = Array(options[:aliases])
14
- @hide = options[: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
- (" " * padding) << sample
101
+ ""
97
102
  else
98
- "#{aliases.join(', ')}, #{sample}"
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! if @check_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
- raise ArgumentError, "Expected #{@type} default value for '#{switch_name}'; got #{@default.inspect} (#{default_type})" unless default_type == @type
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?
@@ -1,5 +1,5 @@
1
1
  class Thor
2
- class Options < Arguments #:nodoc: # rubocop:disable ClassLength
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 parse(args) # rubocop:disable MethodLength
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, SHORT_NUM
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
- @assigns[option.human_name] = parse_peek(switch, option)
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
- require "thor/parser/argument"
2
- require "thor/parser/arguments"
3
- require "thor/parser/option"
4
- require "thor/parser/options"
1
+ require_relative "parser/argument"
2
+ require_relative "parser/arguments"
3
+ require_relative "parser/option"
4
+ require_relative "parser/options"
@@ -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
- require "thor"
2
- require "thor/group"
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/md5"
5
+ require "digest/sha2"
7
6
  require "pathname"
8
7
 
9
- class Thor::Runner < Thor #:nodoc: # rubocop:disable ClassLength
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
- # If a directory name is provided as the argument, look for a 'main.thor'
51
- # command in said directory.
52
- begin
53
- if File.directory?(File.expand_path(name))
54
- base = File.join(name, "main.thor")
55
- package = :directory
56
- contents = open(base, &:read)
57
- else
58
- base = name
59
- package = :file
60
- contents = open(name, &:read)
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] || name =~ %r{^https?://}
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::MD5.hexdigest(name + as),
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
- require "thor/version"
125
+ require_relative "version"
115
126
  say "Thor #{Thor::VERSION}"
116
127
  end
117
128
 
@@ -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
- buffer = "#{status}#{spaces}#{message}"
118
- buffer = "#{buffer}\n" unless buffer.end_with?("\n")
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
- counter = 0
234
- unwrapped.split(" ").inject do |memo, word|
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|irix|hpux)/i
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.include?(answer) ? answer : nil
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|
@@ -1,4 +1,4 @@
1
- require "thor/shell/basic"
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
- stdout.tty?
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
@@ -1,4 +1,4 @@
1
- require "thor/shell/basic"
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