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.
- checksums.yaml +4 -4
- data/README.md +3 -9
- data/lib/thor/actions/create_file.rb +4 -3
- data/lib/thor/actions/create_link.rb +3 -2
- data/lib/thor/actions/directory.rb +8 -18
- data/lib/thor/actions/empty_directory.rb +1 -1
- data/lib/thor/actions/file_manipulation.rb +22 -24
- data/lib/thor/actions/inject_into_file.rb +34 -13
- data/lib/thor/actions.rb +39 -30
- data/lib/thor/base.rb +196 -49
- data/lib/thor/command.rb +34 -18
- data/lib/thor/core_ext/hash_with_indifferent_access.rb +10 -0
- data/lib/thor/error.rb +14 -22
- data/lib/thor/group.rb +13 -2
- data/lib/thor/invocation.rb +2 -1
- 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/argument.rb +17 -1
- data/lib/thor/parser/arguments.rb +35 -15
- data/lib/thor/parser/option.rb +45 -13
- data/lib/thor/parser/options.rb +79 -11
- data/lib/thor/parser.rb +4 -4
- data/lib/thor/rake_compat.rb +3 -2
- data/lib/thor/runner.rb +43 -32
- data/lib/thor/shell/basic.rb +68 -162
- data/lib/thor/shell/color.rb +9 -43
- data/lib/thor/shell/column_printer.rb +29 -0
- data/lib/thor/shell/html.rb +7 -49
- data/lib/thor/shell/lcs_diff.rb +49 -0
- data/lib/thor/shell/table_printer.rb +118 -0
- data/lib/thor/shell/terminal.rb +42 -0
- data/lib/thor/shell/wrapped_printer.rb +38 -0
- data/lib/thor/shell.rb +5 -5
- data/lib/thor/util.rb +25 -8
- data/lib/thor/version.rb +1 -1
- data/lib/thor.rb +182 -17
- data/thor.gemspec +22 -10
- metadata +25 -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
@@ -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, "
|
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
|
#
|
@@ -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!(:
|
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
|
-
|
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
|
-
|
135
|
-
|
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.
|
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
|
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>') # => "<div>"
|
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
data/lib/thor.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
|
-
|
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
|
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
|
-
|
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
|
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, :
|
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, {:
|
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
|
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
|
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 ||=
|
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 ||=
|
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:
|
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
|
-
|
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
|
-
|
416
|
-
|
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.
|
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.
|
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
|
-
|
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
|