thor 0.20.3 → 1.3.2

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.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +3 -9
  3. data/lib/thor/actions/create_file.rb +4 -3
  4. data/lib/thor/actions/create_link.rb +3 -2
  5. data/lib/thor/actions/directory.rb +8 -18
  6. data/lib/thor/actions/empty_directory.rb +1 -1
  7. data/lib/thor/actions/file_manipulation.rb +22 -24
  8. data/lib/thor/actions/inject_into_file.rb +34 -13
  9. data/lib/thor/actions.rb +39 -30
  10. data/lib/thor/base.rb +196 -49
  11. data/lib/thor/command.rb +34 -18
  12. data/lib/thor/core_ext/hash_with_indifferent_access.rb +10 -0
  13. data/lib/thor/error.rb +14 -22
  14. data/lib/thor/group.rb +13 -2
  15. data/lib/thor/invocation.rb +2 -1
  16. data/lib/thor/line_editor/basic.rb +1 -1
  17. data/lib/thor/line_editor/readline.rb +6 -6
  18. data/lib/thor/line_editor.rb +2 -2
  19. data/lib/thor/nested_context.rb +29 -0
  20. data/lib/thor/parser/argument.rb +17 -1
  21. data/lib/thor/parser/arguments.rb +35 -15
  22. data/lib/thor/parser/option.rb +45 -13
  23. data/lib/thor/parser/options.rb +79 -11
  24. data/lib/thor/parser.rb +4 -4
  25. data/lib/thor/rake_compat.rb +3 -2
  26. data/lib/thor/runner.rb +43 -32
  27. data/lib/thor/shell/basic.rb +68 -162
  28. data/lib/thor/shell/color.rb +9 -43
  29. data/lib/thor/shell/column_printer.rb +29 -0
  30. data/lib/thor/shell/html.rb +7 -49
  31. data/lib/thor/shell/lcs_diff.rb +49 -0
  32. data/lib/thor/shell/table_printer.rb +118 -0
  33. data/lib/thor/shell/terminal.rb +42 -0
  34. data/lib/thor/shell/wrapped_printer.rb +38 -0
  35. data/lib/thor/shell.rb +5 -5
  36. data/lib/thor/util.rb +25 -8
  37. data/lib/thor/version.rb +1 -1
  38. data/lib/thor.rb +182 -17
  39. data/thor.gemspec +22 -10
  40. metadata +25 -11
  41. data/CHANGELOG.md +0 -204
  42. data/lib/thor/core_ext/io_binary_read.rb +0 -12
  43. data/lib/thor/core_ext/ordered_hash.rb +0 -129
@@ -0,0 +1,118 @@
1
+ require_relative "column_printer"
2
+ require_relative "terminal"
3
+
4
+ class Thor
5
+ module Shell
6
+ class TablePrinter < ColumnPrinter
7
+ BORDER_SEPARATOR = :separator
8
+
9
+ def initialize(stdout, options = {})
10
+ super
11
+ @formats = []
12
+ @maximas = []
13
+ @colwidth = options[:colwidth]
14
+ @truncate = options[:truncate] == true ? Terminal.terminal_width : options[:truncate]
15
+ @padding = 1
16
+ end
17
+
18
+ def print(array)
19
+ return if array.empty?
20
+
21
+ prepare(array)
22
+
23
+ print_border_separator if options[:borders]
24
+
25
+ array.each do |row|
26
+ if options[:borders] && row == BORDER_SEPARATOR
27
+ print_border_separator
28
+ next
29
+ end
30
+
31
+ sentence = "".dup
32
+
33
+ row.each_with_index do |column, index|
34
+ sentence << format_cell(column, row.size, index)
35
+ end
36
+
37
+ sentence = truncate(sentence)
38
+ sentence << "|" if options[:borders]
39
+ stdout.puts indentation + sentence
40
+
41
+ end
42
+ print_border_separator if options[:borders]
43
+ end
44
+
45
+ private
46
+
47
+ def prepare(array)
48
+ array = array.reject{|row| row == BORDER_SEPARATOR }
49
+
50
+ @formats << "%-#{@colwidth + 2}s".dup if @colwidth
51
+ start = @colwidth ? 1 : 0
52
+
53
+ colcount = array.max { |a, b| a.size <=> b.size }.size
54
+
55
+ start.upto(colcount - 1) do |index|
56
+ maxima = array.map { |row| row[index] ? row[index].to_s.size : 0 }.max
57
+
58
+ @maximas << maxima
59
+ @formats << if options[:borders]
60
+ "%-#{maxima}s".dup
61
+ elsif index == colcount - 1
62
+ # Don't output 2 trailing spaces when printing the last column
63
+ "%-s".dup
64
+ else
65
+ "%-#{maxima + 2}s".dup
66
+ end
67
+ end
68
+
69
+ @formats << "%s"
70
+ end
71
+
72
+ def format_cell(column, row_size, index)
73
+ maxima = @maximas[index]
74
+
75
+ f = if column.is_a?(Numeric)
76
+ if options[:borders]
77
+ # With borders we handle padding separately
78
+ "%#{maxima}s"
79
+ elsif index == row_size - 1
80
+ # Don't output 2 trailing spaces when printing the last column
81
+ "%#{maxima}s"
82
+ else
83
+ "%#{maxima}s "
84
+ end
85
+ else
86
+ @formats[index]
87
+ end
88
+
89
+ cell = "".dup
90
+ cell << "|" + " " * @padding if options[:borders]
91
+ cell << f % column.to_s
92
+ cell << " " * @padding if options[:borders]
93
+ cell
94
+ end
95
+
96
+ def print_border_separator
97
+ separator = @maximas.map do |maxima|
98
+ "+" + "-" * (maxima + 2 * @padding)
99
+ end
100
+ stdout.puts indentation + separator.join + "+"
101
+ end
102
+
103
+ def truncate(string)
104
+ return string unless @truncate
105
+ chars = string.chars.to_a
106
+ if chars.length <= @truncate
107
+ chars.join
108
+ else
109
+ chars[0, @truncate - 3 - @indent].join + "..."
110
+ end
111
+ end
112
+
113
+ def indentation
114
+ " " * @indent
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,42 @@
1
+ class Thor
2
+ module Shell
3
+ module Terminal
4
+ DEFAULT_TERMINAL_WIDTH = 80
5
+
6
+ class << self
7
+ # This code was copied from Rake, available under MIT-LICENSE
8
+ # Copyright (c) 2003, 2004 Jim Weirich
9
+ def terminal_width
10
+ result = if ENV["THOR_COLUMNS"]
11
+ ENV["THOR_COLUMNS"].to_i
12
+ else
13
+ unix? ? dynamic_width : DEFAULT_TERMINAL_WIDTH
14
+ end
15
+ result < 10 ? DEFAULT_TERMINAL_WIDTH : result
16
+ rescue
17
+ DEFAULT_TERMINAL_WIDTH
18
+ end
19
+
20
+ def unix?
21
+ RUBY_PLATFORM =~ /(aix|darwin|linux|(net|free|open)bsd|cygwin|solaris)/i
22
+ end
23
+
24
+ private
25
+
26
+ # Calculate the dynamic width of the terminal
27
+ def dynamic_width
28
+ @dynamic_width ||= (dynamic_width_stty.nonzero? || dynamic_width_tput)
29
+ end
30
+
31
+ def dynamic_width_stty
32
+ `stty size 2>/dev/null`.split[1].to_i
33
+ end
34
+
35
+ def dynamic_width_tput
36
+ `tput cols 2>/dev/null`.to_i
37
+ end
38
+
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,38 @@
1
+ require_relative "column_printer"
2
+ require_relative "terminal"
3
+
4
+ class Thor
5
+ module Shell
6
+ class WrappedPrinter < ColumnPrinter
7
+ def print(message)
8
+ width = Terminal.terminal_width - @indent
9
+ paras = message.split("\n\n")
10
+
11
+ paras.map! do |unwrapped|
12
+ words = unwrapped.split(" ")
13
+ counter = words.first.length
14
+ words.inject do |memo, word|
15
+ word = word.gsub(/\n\005/, "\n").gsub(/\005/, "\n")
16
+ counter = 0 if word.include? "\n"
17
+ if (counter + word.length + 1) < width
18
+ memo = "#{memo} #{word}"
19
+ counter += (word.length + 1)
20
+ else
21
+ memo = "#{memo}\n#{word}"
22
+ counter = word.length
23
+ end
24
+ memo
25
+ end
26
+ end.compact!
27
+
28
+ paras.each do |para|
29
+ para.split("\n").each do |line|
30
+ stdout.puts line.insert(0, " " * @indent)
31
+ end
32
+ stdout.puts unless para == paras.last
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+
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, "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
  #
@@ -75,7 +75,7 @@ class Thor
75
75
  # Allow shell to be shared between invocations.
76
76
  #
77
77
  def _shared_configuration #:nodoc:
78
- super.merge!(:shell => shell)
78
+ super.merge!(shell: shell)
79
79
  end
80
80
  end
81
81
  end
data/lib/thor/util.rb CHANGED
@@ -90,7 +90,7 @@ class Thor
90
90
  def snake_case(str)
91
91
  return str.downcase if str =~ /^[A-Z_]+$/
92
92
  str.gsub(/\B[A-Z]/, '_\&').squeeze("_") =~ /_*(.*)/
93
- $+.downcase
93
+ Regexp.last_match(-1).downcase
94
94
  end
95
95
 
96
96
  # Receives a string and convert it to camel case. camel_case returns CamelCase.
@@ -130,9 +130,10 @@ class Thor
130
130
  #
131
131
  def find_class_and_command_by_namespace(namespace, fallback = true)
132
132
  if namespace.include?(":") # look for a namespaced command
133
- pieces = namespace.split(":")
134
- command = pieces.pop
135
- klass = Thor::Util.find_by_namespace(pieces.join(":"))
133
+ *pieces, command = namespace.split(":")
134
+ namespace = pieces.join(":")
135
+ namespace = "default" if namespace.empty?
136
+ klass = Thor::Base.subclasses.detect { |thor| thor.namespace == namespace && thor.command_exists?(command) }
136
137
  end
137
138
  unless klass # look for a Thor::Group with the right name
138
139
  klass = Thor::Util.find_by_namespace(namespace)
@@ -150,7 +151,7 @@ class Thor
150
151
  # inside the sandbox to avoid namespacing conflicts.
151
152
  #
152
153
  def load_thorfile(path, content = nil, debug = false)
153
- content ||= File.binread(path)
154
+ content ||= File.read(path)
154
155
 
155
156
  begin
156
157
  Thor::Sandbox.class_eval(content, path)
@@ -189,7 +190,7 @@ class Thor
189
190
  # Returns the root where thor files are located, depending on the OS.
190
191
  #
191
192
  def thor_root
192
- File.join(user_home, ".thor").tr('\\', "/")
193
+ File.join(user_home, ".thor").tr("\\", "/")
193
194
  end
194
195
 
195
196
  # Returns the files in the thor root. On Windows thor_root will be something
@@ -211,7 +212,7 @@ class Thor
211
212
  #
212
213
  def globs_for(path)
213
214
  path = escape_globs(path)
214
- ["#{path}/Thorfile", "#{path}/*.thor", "#{path}/tasks/*.thor", "#{path}/lib/tasks/*.thor"]
215
+ ["#{path}/Thorfile", "#{path}/*.thor", "#{path}/tasks/*.thor", "#{path}/lib/tasks/**/*.thor"]
215
216
  end
216
217
 
217
218
  # Return the path to the ruby interpreter taking into account multiple
@@ -236,7 +237,7 @@ class Thor
236
237
  # symlink points to 'ruby_install_name'
237
238
  ruby = alternate_ruby if linked_ruby == ruby_name || linked_ruby == ruby
238
239
  end
239
- rescue NotImplementedError # rubocop:disable HandleExceptions
240
+ rescue NotImplementedError # rubocop:disable Lint/HandleExceptions
240
241
  # just ignore on windows
241
242
  end
242
243
  end
@@ -263,6 +264,22 @@ class Thor
263
264
  def escape_globs(path)
264
265
  path.to_s.gsub(/[*?{}\[\]]/, '\\\\\\&')
265
266
  end
267
+
268
+ # Returns a string that has had any HTML characters escaped.
269
+ #
270
+ # ==== Examples
271
+ #
272
+ # Thor::Util.escape_html('<div>') # => "&lt;div&gt;"
273
+ #
274
+ # ==== Parameters
275
+ # String
276
+ #
277
+ # ==== Returns
278
+ # String
279
+ #
280
+ def escape_html(string)
281
+ CGI.escapeHTML(string)
282
+ end
266
283
  end
267
284
  end
268
285
  end
data/lib/thor/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  class Thor
2
- VERSION = "0.20.3"
2
+ VERSION = "1.3.2"
3
3
  end
data/lib/thor.rb CHANGED
@@ -1,7 +1,7 @@
1
- require "set"
2
- require "thor/base"
1
+ require_relative "thor/base"
3
2
 
4
3
  class Thor
4
+ $thor_runner ||= false
5
5
  class << self
6
6
  # Allows for custom "Command" package naming.
7
7
  #
@@ -65,8 +65,15 @@ class Thor
65
65
 
66
66
  # Defines the long description of the next command.
67
67
  #
68
+ # Long description is by default indented, line-wrapped and repeated whitespace merged.
69
+ # In order to print long description verbatim, with indentation and spacing exactly
70
+ # as found in the code, use the +wrap+ option
71
+ #
72
+ # long_desc 'your very long description', wrap: false
73
+ #
68
74
  # ==== Parameters
69
75
  # long description<String>
76
+ # options<Hash>
70
77
  #
71
78
  def long_desc(long_description, options = {})
72
79
  if options[:for]
@@ -74,6 +81,7 @@ class Thor
74
81
  command.long_description = long_description if long_description
75
82
  else
76
83
  @long_desc = long_description
84
+ @long_desc_wrap = options[:wrap] != false
77
85
  end
78
86
  end
79
87
 
@@ -90,9 +98,14 @@ class Thor
90
98
  # ==== Parameters
91
99
  # Hash[String|Array => Symbol]:: Maps the string or the strings in the array to the given command.
92
100
  #
93
- def map(mappings = nil)
101
+ def map(mappings = nil, **kw)
94
102
  @map ||= from_superclass(:map, {})
95
103
 
104
+ if mappings && !kw.empty?
105
+ mappings = kw.merge!(mappings)
106
+ else
107
+ mappings ||= kw
108
+ end
96
109
  if mappings
97
110
  mappings.each do |key, value|
98
111
  if key.respond_to?(:each)
@@ -128,7 +141,7 @@ class Thor
128
141
  # # magic
129
142
  # end
130
143
  #
131
- # method_option :foo => :bar, :for => :previous_command
144
+ # method_option :foo, :for => :previous_command
132
145
  #
133
146
  # def next_command
134
147
  # # magic
@@ -148,6 +161,9 @@ class Thor
148
161
  # :hide - If you want to hide this option from the help.
149
162
  #
150
163
  def method_option(name, options = {})
164
+ unless [ Symbol, String ].any? { |klass| name.is_a?(klass) }
165
+ raise ArgumentError, "Expected a Symbol or String, got #{name.inspect}"
166
+ end
151
167
  scope = if options[:for]
152
168
  find_and_refresh_command(options[:for]).options
153
169
  else
@@ -158,6 +174,81 @@ class Thor
158
174
  end
159
175
  alias_method :option, :method_option
160
176
 
177
+ # Adds and declares option group for exclusive options in the
178
+ # block and arguments. You can declare options as the outside of the block.
179
+ #
180
+ # If :for is given as option, it allows you to change the options from
181
+ # a previous defined command.
182
+ #
183
+ # ==== Parameters
184
+ # Array[Thor::Option.name]
185
+ # options<Hash>:: :for is applied for previous defined command.
186
+ #
187
+ # ==== Examples
188
+ #
189
+ # exclusive do
190
+ # option :one
191
+ # option :two
192
+ # end
193
+ #
194
+ # Or
195
+ #
196
+ # option :one
197
+ # option :two
198
+ # exclusive :one, :two
199
+ #
200
+ # If you give "--one" and "--two" at the same time ExclusiveArgumentsError
201
+ # will be raised.
202
+ #
203
+ def method_exclusive(*args, &block)
204
+ register_options_relation_for(:method_options,
205
+ :method_exclusive_option_names, *args, &block)
206
+ end
207
+ alias_method :exclusive, :method_exclusive
208
+
209
+ # Adds and declares option group for required at least one of options in the
210
+ # block of arguments. You can declare options as the outside of the block.
211
+ #
212
+ # If :for is given as option, it allows you to change the options from
213
+ # a previous defined command.
214
+ #
215
+ # ==== Parameters
216
+ # Array[Thor::Option.name]
217
+ # options<Hash>:: :for is applied for previous defined command.
218
+ #
219
+ # ==== Examples
220
+ #
221
+ # at_least_one do
222
+ # option :one
223
+ # option :two
224
+ # end
225
+ #
226
+ # Or
227
+ #
228
+ # option :one
229
+ # option :two
230
+ # at_least_one :one, :two
231
+ #
232
+ # If you do not give "--one" and "--two" AtLeastOneRequiredArgumentError
233
+ # will be raised.
234
+ #
235
+ # You can use at_least_one and exclusive at the same time.
236
+ #
237
+ # exclusive do
238
+ # at_least_one do
239
+ # option :one
240
+ # option :two
241
+ # end
242
+ # end
243
+ #
244
+ # Then it is required either only one of "--one" or "--two".
245
+ #
246
+ def method_at_least_one(*args, &block)
247
+ register_options_relation_for(:method_options,
248
+ :method_at_least_one_option_names, *args, &block)
249
+ end
250
+ alias_method :at_least_one, :method_at_least_one
251
+
161
252
  # Prints help information for the given command.
162
253
  #
163
254
  # ==== Parameters
@@ -170,12 +261,19 @@ class Thor
170
261
  handle_no_command_error(meth) unless command
171
262
 
172
263
  shell.say "Usage:"
173
- shell.say " #{banner(command)}"
264
+ shell.say " #{banner(command).split("\n").join("\n ")}"
174
265
  shell.say
175
266
  class_options_help(shell, nil => command.options.values)
267
+ print_exclusive_options(shell, command)
268
+ print_at_least_one_required_options(shell, command)
269
+
176
270
  if command.long_description
177
271
  shell.say "Description:"
178
- shell.print_wrapped(command.long_description, :indent => 2)
272
+ if command.wrap_long_description
273
+ shell.print_wrapped(command.long_description, indent: 2)
274
+ else
275
+ shell.say command.long_description
276
+ end
179
277
  else
180
278
  shell.say command.description
181
279
  end
@@ -192,7 +290,7 @@ class Thor
192
290
  Thor::Util.thor_classes_in(self).each do |klass|
193
291
  list += klass.printable_commands(false)
194
292
  end
195
- list.sort! { |a, b| a[0] <=> b[0] }
293
+ sort_commands!(list)
196
294
 
197
295
  if defined?(@package_name) && @package_name
198
296
  shell.say "#{@package_name} commands:"
@@ -200,9 +298,11 @@ class Thor
200
298
  shell.say "Commands:"
201
299
  end
202
300
 
203
- shell.print_table(list, :indent => 2, :truncate => true)
301
+ shell.print_table(list, indent: 2, truncate: true)
204
302
  shell.say
205
303
  class_options_help(shell)
304
+ print_exclusive_options(shell)
305
+ print_at_least_one_required_options(shell)
206
306
  end
207
307
 
208
308
  # Returns commands ready to be printed.
@@ -233,7 +333,7 @@ class Thor
233
333
 
234
334
  define_method(subcommand) do |*args|
235
335
  args, opts = Thor::Arguments.split(args)
236
- invoke_args = [args, opts, {:invoked_via_subcommand => true, :class_options => options}]
336
+ invoke_args = [args, opts, {invoked_via_subcommand: true, class_options: options}]
237
337
  invoke_args.unshift "help" if opts.delete("--help") || opts.delete("-h")
238
338
  invoke subcommand_class, *invoke_args
239
339
  end
@@ -318,7 +418,7 @@ class Thor
318
418
  # ==== Parameters
319
419
  # Symbol ...:: A list of commands that should be affected.
320
420
  def stop_on_unknown_option!(*command_names)
321
- stop_on_unknown_option.merge(command_names)
421
+ @stop_on_unknown_option = stop_on_unknown_option | command_names
322
422
  end
323
423
 
324
424
  def stop_on_unknown_option?(command) #:nodoc:
@@ -332,26 +432,77 @@ class Thor
332
432
  # ==== Parameters
333
433
  # Symbol ...:: A list of commands that should be affected.
334
434
  def disable_required_check!(*command_names)
335
- disable_required_check.merge(command_names)
435
+ @disable_required_check = disable_required_check | command_names
336
436
  end
337
437
 
338
438
  def disable_required_check?(command) #:nodoc:
339
439
  command && disable_required_check.include?(command.name.to_sym)
340
440
  end
341
441
 
442
+ # Checks if a specified command exists.
443
+ #
444
+ # ==== Parameters
445
+ # command_name<String>:: The name of the command to check for existence.
446
+ #
447
+ # ==== Returns
448
+ # Boolean:: +true+ if the command exists, +false+ otherwise.
449
+ def command_exists?(command_name) #:nodoc:
450
+ commands.keys.include?(normalize_command_name(command_name))
451
+ end
452
+
342
453
  protected
343
454
 
455
+ # Returns this class exclusive options array set.
456
+ #
457
+ # ==== Returns
458
+ # Array[Array[Thor::Option.name]]
459
+ #
460
+ def method_exclusive_option_names #:nodoc:
461
+ @method_exclusive_option_names ||= []
462
+ end
463
+
464
+ # Returns this class at least one of required options array set.
465
+ #
466
+ # ==== Returns
467
+ # Array[Array[Thor::Option.name]]
468
+ #
469
+ def method_at_least_one_option_names #:nodoc:
470
+ @method_at_least_one_option_names ||= []
471
+ end
472
+
344
473
  def stop_on_unknown_option #:nodoc:
345
- @stop_on_unknown_option ||= Set.new
474
+ @stop_on_unknown_option ||= []
346
475
  end
347
476
 
348
477
  # help command has the required check disabled by default.
349
478
  def disable_required_check #:nodoc:
350
- @disable_required_check ||= Set.new([:help])
479
+ @disable_required_check ||= [:help]
480
+ end
481
+
482
+ def print_exclusive_options(shell, command = nil) # :nodoc:
483
+ opts = []
484
+ opts = command.method_exclusive_option_names unless command.nil?
485
+ opts += class_exclusive_option_names
486
+ unless opts.empty?
487
+ shell.say "Exclusive Options:"
488
+ shell.print_table(opts.map{ |ex| ex.map{ |e| "--#{e}"}}, indent: 2 )
489
+ shell.say
490
+ end
491
+ end
492
+
493
+ def print_at_least_one_required_options(shell, command = nil) # :nodoc:
494
+ opts = []
495
+ opts = command.method_at_least_one_option_names unless command.nil?
496
+ opts += class_at_least_one_option_names
497
+ unless opts.empty?
498
+ shell.say "Required At Least One:"
499
+ shell.print_table(opts.map{ |ex| ex.map{ |e| "--#{e}"}}, indent: 2 )
500
+ shell.say
501
+ end
351
502
  end
352
503
 
353
504
  # The method responsible for dispatching given the args.
354
- def dispatch(meth, given_args, given_opts, config) #:nodoc: # rubocop:disable MethodLength
505
+ def dispatch(meth, given_args, given_opts, config) #:nodoc:
355
506
  meth ||= retrieve_command_name(given_args)
356
507
  command = all_commands[normalize_command_name(meth)]
357
508
 
@@ -393,7 +544,9 @@ class Thor
393
544
  # the namespace should be displayed as arguments.
394
545
  #
395
546
  def banner(command, namespace = nil, subcommand = false)
396
- "#{basename} #{command.formatted_usage(self, $thor_runner, subcommand)}"
547
+ command.formatted_usage(self, $thor_runner, subcommand).split("\n").map do |formatted_usage|
548
+ "#{basename} #{formatted_usage}"
549
+ end.join("\n")
397
550
  end
398
551
 
399
552
  def baseclass #:nodoc:
@@ -408,12 +561,16 @@ class Thor
408
561
  @usage ||= nil
409
562
  @desc ||= nil
410
563
  @long_desc ||= nil
564
+ @long_desc_wrap ||= nil
411
565
  @hide ||= nil
412
566
 
413
567
  if @usage && @desc
414
568
  base_class = @hide ? Thor::HiddenCommand : Thor::Command
415
- commands[meth] = base_class.new(meth, @desc, @long_desc, @usage, method_options)
416
- @usage, @desc, @long_desc, @method_options, @hide = nil
569
+ relations = {exclusive_option_names: method_exclusive_option_names,
570
+ at_least_one_option_names: method_at_least_one_option_names}
571
+ commands[meth] = base_class.new(meth, @desc, @long_desc, @long_desc_wrap, @usage, method_options, relations)
572
+ @usage, @desc, @long_desc, @long_desc_wrap, @method_options, @hide = nil
573
+ @method_exclusive_option_names, @method_at_least_one_option_names = nil
417
574
  true
418
575
  elsif all_commands[meth] || meth == "method_missing"
419
576
  true
@@ -488,6 +645,14 @@ class Thor
488
645
  "
489
646
  end
490
647
  alias_method :subtask_help, :subcommand_help
648
+
649
+ # Sort the commands, lexicographically by default.
650
+ #
651
+ # Can be overridden in the subclass to change the display order of the
652
+ # commands.
653
+ def sort_commands!(list)
654
+ list.sort! { |a, b| a[0] <=> b[0] }
655
+ end
491
656
  end
492
657
 
493
658
  include Thor::Base
data/thor.gemspec CHANGED
@@ -4,18 +4,30 @@ $LOAD_PATH.unshift lib unless $LOAD_PATH.include?(lib)
4
4
  require "thor/version"
5
5
 
6
6
  Gem::Specification.new do |spec|
7
- spec.add_development_dependency "bundler", "~> 1.0"
7
+ spec.name = "thor"
8
+ spec.version = Thor::VERSION
9
+ spec.licenses = %w(MIT)
8
10
  spec.authors = ["Yehuda Katz", "José Valim"]
9
- spec.description = "Thor is a toolkit for building powerful command-line interfaces."
10
11
  spec.email = "ruby-thor@googlegroups.com"
11
- spec.executables = %w(thor)
12
- spec.files = %w(.document thor.gemspec) + Dir["*.md", "bin/*", "lib/**/*.rb"]
13
12
  spec.homepage = "http://whatisthor.com/"
14
- spec.licenses = %w(MIT)
15
- spec.name = "thor"
16
- spec.require_paths = %w(lib)
17
- spec.required_ruby_version = ">= 1.8.7"
18
- spec.required_rubygems_version = ">= 1.3.5"
13
+ spec.description = "Thor is a toolkit for building powerful command-line interfaces."
19
14
  spec.summary = spec.description
20
- spec.version = Thor::VERSION
15
+
16
+ spec.metadata = {
17
+ "bug_tracker_uri" => "https://github.com/rails/thor/issues",
18
+ "changelog_uri" => "https://github.com/rails/thor/releases/tag/v#{Thor::VERSION}",
19
+ "documentation_uri" => "http://whatisthor.com/",
20
+ "source_code_uri" => "https://github.com/rails/thor/tree/v#{Thor::VERSION}",
21
+ "wiki_uri" => "https://github.com/rails/thor/wiki",
22
+ "rubygems_mfa_required" => "true",
23
+ }
24
+
25
+ spec.required_ruby_version = ">= 2.6.0"
26
+ spec.required_rubygems_version = ">= 1.3.5"
27
+
28
+ spec.files = %w(.document thor.gemspec) + Dir["*.md", "bin/*", "lib/**/*.rb"]
29
+ spec.executables = %w(thor)
30
+ spec.require_paths = %w(lib)
31
+
32
+ spec.add_development_dependency "bundler", ">= 1.0", "< 3"
21
33
  end