thor 0.20.1 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -49,24 +49,32 @@ class Thor
49
49
 
50
50
  formatted ||= "".dup
51
51
 
52
- # Add usage with required arguments
53
- formatted << if klass && !klass.arguments.empty?
54
- usage.to_s.gsub(/^#{name}/) do |match|
55
- match << " " << klass.arguments.map(&:usage).compact.join(" ")
56
- end
57
- else
58
- usage.to_s
59
- end
52
+ Array(usage).map do |specific_usage|
53
+ formatted_specific_usage = formatted
60
54
 
61
- # Add required options
62
- formatted << " #{required_options}"
55
+ formatted_specific_usage += required_arguments_for(klass, specific_usage)
63
56
 
64
- # Strip and go!
65
- formatted.strip
57
+ # Add required options
58
+ formatted_specific_usage += " #{required_options}"
59
+
60
+ # Strip and go!
61
+ formatted_specific_usage.strip
62
+ end.join("\n")
66
63
  end
67
64
 
68
65
  protected
69
66
 
67
+ # Add usage with required arguments
68
+ def required_arguments_for(klass, usage)
69
+ if klass && !klass.arguments.empty?
70
+ usage.to_s.gsub(/^#{name}/) do |match|
71
+ match << " " << klass.arguments.map(&:usage).compact.join(" ")
72
+ end
73
+ else
74
+ usage.to_s
75
+ end
76
+ end
77
+
70
78
  def not_debugging?(instance)
71
79
  !(instance.class.respond_to?(:debugging) && instance.class.debugging)
72
80
  end
@@ -97,8 +105,7 @@ class Thor
97
105
  def handle_argument_error?(instance, error, caller)
98
106
  not_debugging?(instance) && (error.message =~ /wrong number of arguments/ || error.message =~ /given \d*, expected \d*/) && begin
99
107
  saned = sans_backtrace(error.backtrace, caller)
100
- # Ruby 1.9 always include the called method in the backtrace
101
- saned.empty? || (saned.size == 1 && RUBY_VERSION >= "1.9")
108
+ saned.empty? || saned.size == 1
102
109
  end
103
110
  end
104
111
 
@@ -1,24 +1,18 @@
1
1
  class Thor
2
- Correctable =
3
- begin
4
- require 'did_you_mean'
5
-
6
- module DidYouMean
7
- # In order to support versions of Ruby that don't have keyword
8
- # arguments, we need our own spell checker class that doesn't take key
9
- # words. Even though this code wouldn't be hit because of the check
10
- # above, it's still necessary because the interpreter would otherwise be
11
- # unable to parse the file.
12
- class NoKwargSpellChecker < SpellChecker
13
- def initialize(dictionary)
14
- @dictionary = dictionary
15
- end
16
- end
17
- end
18
-
19
- DidYouMean::Correctable
20
- rescue LoadError
21
- 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
+ DidYouMean::Correctable
15
+ end
22
16
 
23
17
  # Thor::Error is raised when it's caused by wrong usage of thor classes. Those
24
18
  # errors have their backtrace suppressed and are nicely shown to the user.
@@ -43,7 +37,7 @@ class Thor
43
37
  end
44
38
 
45
39
  def spell_checker
46
- DidYouMean::NoKwargSpellChecker.new(error.all_commands)
40
+ NoKwargSpellChecker.new(error.all_commands)
47
41
  end
48
42
  end
49
43
 
@@ -85,8 +79,7 @@ class Thor
85
79
  end
86
80
 
87
81
  def spell_checker
88
- @spell_checker ||=
89
- DidYouMean::NoKwargSpellChecker.new(error.switches)
82
+ @spell_checker ||= NoKwargSpellChecker.new(error.switches)
90
83
  end
91
84
  end
92
85
 
@@ -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
@@ -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
 
@@ -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
@@ -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
@@ -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,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"
@@ -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
@@ -111,7 +112,7 @@ class Thor
111
112
 
112
113
  def validate!
113
114
  raise ArgumentError, "An option cannot be boolean and required." if boolean? && required?
114
- validate_default_type! if @check_default_type
115
+ validate_default_type!
115
116
  end
116
117
 
117
118
  def validate_default_type!
@@ -128,7 +129,19 @@ class Thor
128
129
  @default.class.name.downcase.to_sym
129
130
  end
130
131
 
131
- raise ArgumentError, "Expected #{@type} default value for '#{switch_name}'; got #{@default.inspect} (#{default_type})" unless default_type == @type
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?
@@ -97,7 +97,8 @@ class Thor
97
97
 
98
98
  switch = normalize_switch(switch)
99
99
  option = switch_option(switch)
100
- @assigns[option.human_name] = parse_peek(switch, option)
100
+ result = parse_peek(switch, option)
101
+ assign_result!(option, result)
101
102
  elsif @stop_on_unknown
102
103
  @parsing_options = false
103
104
  @extra << shifted
@@ -132,6 +133,16 @@ class Thor
132
133
 
133
134
  protected
134
135
 
136
+ def assign_result!(option, result)
137
+ if option.repeatable && option.type == :hash
138
+ (@assigns[option.human_name] ||= {}).merge!(result)
139
+ elsif option.repeatable
140
+ (@assigns[option.human_name] ||= []) << result
141
+ else
142
+ @assigns[option.human_name] = result
143
+ end
144
+ end
145
+
135
146
  # Check if the current value in peek is a registered switch.
136
147
  #
137
148
  # Two booleans are returned. The first is true if the current value
@@ -161,7 +172,7 @@ class Thor
161
172
  end
162
173
 
163
174
  def switch?(arg)
164
- switch_option(normalize_switch(arg))
175
+ !switch_option(normalize_switch(arg)).nil?
165
176
  end
166
177
 
167
178
  def switch_option(arg)
@@ -194,7 +205,7 @@ class Thor
194
205
  shift
195
206
  false
196
207
  else
197
- !no_or_skip?(switch)
208
+ @switches.key?(switch) || !no_or_skip?(switch)
198
209
  end
199
210
  else
200
211
  @switches.key?(switch) || !no_or_skip?(switch)
@@ -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)
@@ -1,12 +1,13 @@
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
5
  require "digest/md5"
7
6
  require "pathname"
8
7
 
9
8
  class Thor::Runner < Thor #:nodoc: # rubocop:disable ClassLength
9
+ autoload :OpenURI, "open-uri"
10
+
10
11
  map "-T" => :list, "-i" => :install, "-u" => :update, "-v" => :version
11
12
 
12
13
  def self.banner(command, all = false, subcommand = false)
@@ -111,7 +112,7 @@ class Thor::Runner < Thor #:nodoc: # rubocop:disable ClassLength
111
112
 
112
113
  desc "version", "Show Thor version"
113
114
  def version
114
- require "thor/version"
115
+ require_relative "version"
115
116
  say "Thor #{Thor::VERSION}"
116
117
  end
117
118
 
@@ -24,9 +24,9 @@ class Thor
24
24
  SHELL_DELEGATED_METHODS = [:ask, :error, :set_color, :yes?, :no?, :say, :say_status, :print_in_columns, :print_table, :print_wrapped, :file_collision, :terminal_width]
25
25
  attr_writer :shell
26
26
 
27
- autoload :Basic, "thor/shell/basic"
28
- autoload :Color, "thor/shell/color"
29
- autoload :HTML, "thor/shell/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
  #
@@ -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
 
@@ -230,8 +232,9 @@ class Thor
230
232
  paras = message.split("\n\n")
231
233
 
232
234
  paras.map! do |unwrapped|
233
- counter = 0
234
- unwrapped.split(" ").inject do |memo, word|
235
+ words = unwrapped.split(" ")
236
+ counter = words.first.length
237
+ words.inject do |memo, word|
235
238
  word = word.gsub(/\n\005/, "\n").gsub(/\005/, "\n")
236
239
  counter = 0 if word.include? "\n"
237
240
  if (counter + word.length + 1) < width
@@ -451,16 +454,25 @@ class Thor
451
454
 
452
455
  def ask_filtered(statement, color, options)
453
456
  answer_set = options[:limited_to]
457
+ case_insensitive = options.fetch(:case_insensitive, false)
454
458
  correct_answer = nil
455
459
  until correct_answer
456
460
  answers = answer_set.join(", ")
457
461
  answer = ask_simply("#{statement} [#{answers}]", color, options)
458
- correct_answer = answer_set.include?(answer) ? answer : nil
462
+ correct_answer = answer_match(answer_set, answer, case_insensitive)
459
463
  say("Your response must be one of: [#{answers}]. Please try again.") unless correct_answer
460
464
  end
461
465
  correct_answer
462
466
  end
463
467
 
468
+ def answer_match(possibilities, answer, case_insensitive)
469
+ if case_insensitive
470
+ possibilities.detect{ |possibility| possibility.downcase == answer.downcase }
471
+ else
472
+ possibilities.detect{ |possibility| possibility == answer }
473
+ end
474
+ end
475
+
464
476
  def merge(destination, content) #:nodoc:
465
477
  require "tempfile"
466
478
  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?
101
109
  end
102
110
 
103
111
  # Overwrite show_diff to show diff with colors if Diff::LCS is