thor 1.2.2 → 1.4.0
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 +2 -2
- data/lib/thor/actions/create_file.rb +2 -1
- data/lib/thor/actions/directory.rb +1 -1
- data/lib/thor/actions/empty_directory.rb +1 -1
- data/lib/thor/actions/file_manipulation.rb +51 -19
- data/lib/thor/actions/inject_into_file.rb +15 -4
- data/lib/thor/actions.rb +14 -15
- data/lib/thor/base.rb +136 -9
- data/lib/thor/command.rb +13 -4
- data/lib/thor/core_ext/hash_with_indifferent_access.rb +4 -0
- data/lib/thor/error.rb +18 -23
- data/lib/thor/group.rb +11 -0
- data/lib/thor/invocation.rb +1 -1
- data/lib/thor/nested_context.rb +2 -2
- data/lib/thor/parser/argument.rb +17 -1
- data/lib/thor/parser/arguments.rb +32 -16
- data/lib/thor/parser/option.rb +21 -6
- data/lib/thor/parser/options.rb +44 -5
- data/lib/thor/runner.rb +12 -12
- data/lib/thor/shell/basic.rb +37 -165
- data/lib/thor/shell/color.rb +4 -46
- data/lib/thor/shell/column_printer.rb +29 -0
- data/lib/thor/shell/html.rb +4 -46
- 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 +1 -1
- data/lib/thor/util.rb +4 -3
- data/lib/thor/version.rb +1 -1
- data/lib/thor.rb +165 -7
- data/thor.gemspec +14 -10
- metadata +11 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 50c78a27ef16cfc96930bc60d8637e86acd69c94b932cc745bb2ed5c3e1605c8
|
4
|
+
data.tar.gz: 00b8a88f938047a55fe3ffadb6bd019666d82813ff584209a338bd8e6d3f16a7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 86fb64f2b698f06f5c7a0bd14523338802b374ae3d42879d71f94d542598216595176ce744b9ea84196d87657ec4cdaa9f73954ad15124b89109ee87a304eadc
|
7
|
+
data.tar.gz: 81f3b4ce1bca3d43efe2aecf00c88372b6673e3a82706e75ef018cc17badf3b3f2a267fe926b86225b112bbe1bd6e0257115327aa006984c89d8ddc434b88d56
|
data/README.md
CHANGED
@@ -15,7 +15,7 @@ users.
|
|
15
15
|
|
16
16
|
Please note: Thor, by design, is a system tool created to allow seamless file and url
|
17
17
|
access, which should not receive application user input. It relies on [open-uri][open-uri],
|
18
|
-
which combined with application user input would provide a command injection attack
|
18
|
+
which, combined with application user input, would provide a command injection attack
|
19
19
|
vector.
|
20
20
|
|
21
21
|
[rake]: https://github.com/ruby/rake
|
@@ -27,7 +27,7 @@ Installation
|
|
27
27
|
|
28
28
|
Usage and documentation
|
29
29
|
-----------------------
|
30
|
-
Please see the [wiki][] for basic usage and other documentation on using Thor. You can also
|
30
|
+
Please see the [wiki][] for basic usage and other documentation on using Thor. You can also check out the [official homepage][homepage].
|
31
31
|
|
32
32
|
[wiki]: https://github.com/rails/thor/wiki
|
33
33
|
[homepage]: http://whatisthor.com/
|
@@ -43,7 +43,8 @@ class Thor
|
|
43
43
|
# Boolean:: true if it is identical, false otherwise.
|
44
44
|
#
|
45
45
|
def identical?
|
46
|
-
|
46
|
+
# binread uses ASCII-8BIT, so to avoid false negatives, the string must use the same
|
47
|
+
exists? && File.binread(destination) == String.new(render).force_encoding("ASCII-8BIT")
|
47
48
|
end
|
48
49
|
|
49
50
|
# Holds the content to be added to the file.
|
@@ -58,7 +58,7 @@ class Thor
|
|
58
58
|
def initialize(base, source, destination = nil, config = {}, &block)
|
59
59
|
@source = File.expand_path(Dir[Util.escape_globs(base.find_in_source_paths(source.to_s))].first)
|
60
60
|
@block = block
|
61
|
-
super(base, destination, {:
|
61
|
+
super(base, destination, {recursive: true}.merge(config))
|
62
62
|
end
|
63
63
|
|
64
64
|
def invoke!
|
@@ -10,7 +10,6 @@ class Thor
|
|
10
10
|
# destination<String>:: the relative path to the destination root.
|
11
11
|
# config<Hash>:: give :verbose => false to not log the status, and
|
12
12
|
# :mode => :preserve, to preserve the file mode from the source.
|
13
|
-
|
14
13
|
#
|
15
14
|
# ==== Examples
|
16
15
|
#
|
@@ -66,12 +65,15 @@ class Thor
|
|
66
65
|
# ==== Parameters
|
67
66
|
# source<String>:: the address of the given content.
|
68
67
|
# destination<String>:: the relative path to the destination root.
|
69
|
-
# config<Hash>:: give :verbose => false to not log the status
|
68
|
+
# config<Hash>:: give :verbose => false to not log the status, and
|
69
|
+
# :http_headers => <Hash> to add headers to an http request.
|
70
70
|
#
|
71
71
|
# ==== Examples
|
72
72
|
#
|
73
73
|
# get "http://gist.github.com/103208", "doc/README"
|
74
74
|
#
|
75
|
+
# get "http://gist.github.com/103208", "doc/README", :http_headers => {"Content-Type" => "application/json"}
|
76
|
+
#
|
75
77
|
# get "http://gist.github.com/103208" do |content|
|
76
78
|
# content.split("\n").first
|
77
79
|
# end
|
@@ -82,7 +84,7 @@ class Thor
|
|
82
84
|
|
83
85
|
render = if source =~ %r{^https?\://}
|
84
86
|
require "open-uri"
|
85
|
-
URI.send(:open, source) { |input| input.binmode.read }
|
87
|
+
URI.send(:open, source, config.fetch(:http_headers, {})) { |input| input.binmode.read }
|
86
88
|
else
|
87
89
|
source = File.expand_path(find_in_source_paths(source.to_s))
|
88
90
|
File.open(source) { |input| input.binmode.read }
|
@@ -120,12 +122,7 @@ class Thor
|
|
120
122
|
context = config.delete(:context) || instance_eval("binding")
|
121
123
|
|
122
124
|
create_file destination, nil, config do
|
123
|
-
|
124
|
-
capturable_erb = if match && match[1] >= "2.2.0" # Ruby 2.6+
|
125
|
-
CapturableERB.new(::File.binread(source), :trim_mode => "-", :eoutvar => "@output_buffer")
|
126
|
-
else
|
127
|
-
CapturableERB.new(::File.binread(source), nil, "-", "@output_buffer")
|
128
|
-
end
|
125
|
+
capturable_erb = CapturableERB.new(::File.binread(source), trim_mode: "-", eoutvar: "@output_buffer")
|
129
126
|
content = capturable_erb.tap do |erb|
|
130
127
|
erb.filename = source
|
131
128
|
end.result(context)
|
@@ -245,6 +242,35 @@ class Thor
|
|
245
242
|
insert_into_file(path, *(args << config), &block)
|
246
243
|
end
|
247
244
|
|
245
|
+
# Run a regular expression replacement on a file, raising an error if the
|
246
|
+
# contents of the file are not changed.
|
247
|
+
#
|
248
|
+
# ==== Parameters
|
249
|
+
# path<String>:: path of the file to be changed
|
250
|
+
# flag<Regexp|String>:: the regexp or string to be replaced
|
251
|
+
# replacement<String>:: the replacement, can be also given as a block
|
252
|
+
# config<Hash>:: give :verbose => false to not log the status, and
|
253
|
+
# :force => true, to force the replacement regardless of runner behavior.
|
254
|
+
#
|
255
|
+
# ==== Example
|
256
|
+
#
|
257
|
+
# gsub_file! 'app/controllers/application_controller.rb', /#\s*(filter_parameter_logging :password)/, '\1'
|
258
|
+
#
|
259
|
+
# gsub_file! 'README', /rake/, :green do |match|
|
260
|
+
# match << " no more. Use thor!"
|
261
|
+
# end
|
262
|
+
#
|
263
|
+
def gsub_file!(path, flag, *args, &block)
|
264
|
+
config = args.last.is_a?(Hash) ? args.pop : {}
|
265
|
+
|
266
|
+
return unless behavior == :invoke || config.fetch(:force, false)
|
267
|
+
|
268
|
+
path = File.expand_path(path, destination_root)
|
269
|
+
say_status :gsub, relative_to_original_destination_root(path), config.fetch(:verbose, true)
|
270
|
+
|
271
|
+
actually_gsub_file(path, flag, args, true, &block) unless options[:pretend]
|
272
|
+
end
|
273
|
+
|
248
274
|
# Run a regular expression replacement on a file.
|
249
275
|
#
|
250
276
|
# ==== Parameters
|
@@ -270,16 +296,11 @@ class Thor
|
|
270
296
|
path = File.expand_path(path, destination_root)
|
271
297
|
say_status :gsub, relative_to_original_destination_root(path), config.fetch(:verbose, true)
|
272
298
|
|
273
|
-
unless options[:pretend]
|
274
|
-
content = File.binread(path)
|
275
|
-
content.gsub!(flag, *args, &block)
|
276
|
-
File.open(path, "wb") { |file| file.write(content) }
|
277
|
-
end
|
299
|
+
actually_gsub_file(path, flag, args, false, &block) unless options[:pretend]
|
278
300
|
end
|
279
301
|
|
280
|
-
# Uncomment all lines matching a given regex.
|
281
|
-
#
|
282
|
-
# between the comment hash and the beginning of the line.
|
302
|
+
# Uncomment all lines matching a given regex. Preserves indentation before
|
303
|
+
# the comment hash and removes the hash and any immediate following space.
|
283
304
|
#
|
284
305
|
# ==== Parameters
|
285
306
|
# path<String>:: path of the file to be changed
|
@@ -293,7 +314,7 @@ class Thor
|
|
293
314
|
def uncomment_lines(path, flag, *args)
|
294
315
|
flag = flag.respond_to?(:source) ? flag.source : flag
|
295
316
|
|
296
|
-
gsub_file(path, /^(\s*)#[[:blank:]]
|
317
|
+
gsub_file(path, /^(\s*)#[[:blank:]]?(.*#{flag})/, '\1\2', *args)
|
297
318
|
end
|
298
319
|
|
299
320
|
# Comment all lines matching a given regex. It will leave the space
|
@@ -352,7 +373,7 @@ class Thor
|
|
352
373
|
end
|
353
374
|
|
354
375
|
def with_output_buffer(buf = "".dup) #:nodoc:
|
355
|
-
raise ArgumentError, "Buffer
|
376
|
+
raise ArgumentError, "Buffer cannot be a frozen object" if buf.frozen?
|
356
377
|
old_buffer = output_buffer
|
357
378
|
self.output_buffer = buf
|
358
379
|
yield
|
@@ -361,6 +382,17 @@ class Thor
|
|
361
382
|
self.output_buffer = old_buffer
|
362
383
|
end
|
363
384
|
|
385
|
+
def actually_gsub_file(path, flag, args, error_on_no_change, &block)
|
386
|
+
content = File.binread(path)
|
387
|
+
success = content.gsub!(flag, *args, &block)
|
388
|
+
|
389
|
+
if success.nil? && error_on_no_change
|
390
|
+
raise Thor::Error, "The content of #{path} did not change"
|
391
|
+
end
|
392
|
+
|
393
|
+
File.open(path, "wb") { |file| file.write(content) }
|
394
|
+
end
|
395
|
+
|
364
396
|
# Thor::Actions#capture depends on what kind of buffer is used in ERB.
|
365
397
|
# Thus CapturableERB fixes ERB to use String buffer.
|
366
398
|
class CapturableERB < ERB
|
@@ -21,7 +21,7 @@ class Thor
|
|
21
21
|
# gems.split(" ").map{ |gem| " config.gem :#{gem}" }.join("\n")
|
22
22
|
# end
|
23
23
|
#
|
24
|
-
WARNINGS = {
|
24
|
+
WARNINGS = {unchanged_no_flag: "File unchanged! Either the supplied flag value not found or the content has already been inserted!"}
|
25
25
|
|
26
26
|
def insert_into_file(destination, *args, &block)
|
27
27
|
data = block_given? ? block : args.shift
|
@@ -37,7 +37,7 @@ class Thor
|
|
37
37
|
attr_reader :replacement, :flag, :behavior
|
38
38
|
|
39
39
|
def initialize(base, destination, data, config)
|
40
|
-
super(base, destination, {:
|
40
|
+
super(base, destination, {verbose: true}.merge(config))
|
41
41
|
|
42
42
|
@behavior, @flag = if @config.key?(:after)
|
43
43
|
[:after, @config.delete(:after)]
|
@@ -59,6 +59,8 @@ class Thor
|
|
59
59
|
if exists?
|
60
60
|
if replace!(/#{flag}/, content, config[:force])
|
61
61
|
say_status(:invoke)
|
62
|
+
elsif replacement_present?
|
63
|
+
say_status(:unchanged, color: :blue)
|
62
64
|
else
|
63
65
|
say_status(:unchanged, warning: WARNINGS[:unchanged_no_flag], color: :red)
|
64
66
|
end
|
@@ -96,6 +98,8 @@ class Thor
|
|
96
98
|
end
|
97
99
|
elsif warning
|
98
100
|
warning
|
101
|
+
elsif behavior == :unchanged
|
102
|
+
:unchanged
|
99
103
|
else
|
100
104
|
:subtract
|
101
105
|
end
|
@@ -103,11 +107,18 @@ class Thor
|
|
103
107
|
super(status, (color || config[:verbose]))
|
104
108
|
end
|
105
109
|
|
110
|
+
def content
|
111
|
+
@content ||= File.read(destination)
|
112
|
+
end
|
113
|
+
|
114
|
+
def replacement_present?
|
115
|
+
content.include?(replacement)
|
116
|
+
end
|
117
|
+
|
106
118
|
# Adds the content to the file.
|
107
119
|
#
|
108
120
|
def replace!(regexp, string, force)
|
109
|
-
|
110
|
-
if force || !content.include?(replacement)
|
121
|
+
if force || !replacement_present?
|
111
122
|
success = content.gsub!(regexp, string)
|
112
123
|
|
113
124
|
File.open(destination, "wb") { |file| file.write(content) } unless pretend?
|
data/lib/thor/actions.rb
CHANGED
@@ -46,17 +46,17 @@ class Thor
|
|
46
46
|
# Add runtime options that help actions execution.
|
47
47
|
#
|
48
48
|
def add_runtime_options!
|
49
|
-
class_option :force, :
|
50
|
-
:
|
49
|
+
class_option :force, type: :boolean, aliases: "-f", group: :runtime,
|
50
|
+
desc: "Overwrite files that already exist"
|
51
51
|
|
52
|
-
class_option :pretend, :
|
53
|
-
:
|
52
|
+
class_option :pretend, type: :boolean, aliases: "-p", group: :runtime,
|
53
|
+
desc: "Run but do not make any changes"
|
54
54
|
|
55
|
-
class_option :quiet, :
|
56
|
-
:
|
55
|
+
class_option :quiet, type: :boolean, aliases: "-q", group: :runtime,
|
56
|
+
desc: "Suppress status output"
|
57
57
|
|
58
|
-
class_option :skip, :
|
59
|
-
:
|
58
|
+
class_option :skip, type: :boolean, aliases: "-s", group: :runtime,
|
59
|
+
desc: "Skip files that already exist"
|
60
60
|
end
|
61
61
|
end
|
62
62
|
|
@@ -113,9 +113,9 @@ class Thor
|
|
113
113
|
#
|
114
114
|
def relative_to_original_destination_root(path, remove_dot = true)
|
115
115
|
root = @destination_stack[0]
|
116
|
-
if path.start_with?(root) && [File::SEPARATOR, File::ALT_SEPARATOR, nil,
|
116
|
+
if path.start_with?(root) && [File::SEPARATOR, File::ALT_SEPARATOR, nil, ""].include?(path[root.size..root.size])
|
117
117
|
path = path.dup
|
118
|
-
path[0...root.size] =
|
118
|
+
path[0...root.size] = "."
|
119
119
|
remove_dot ? (path[2..-1] || "") : path
|
120
120
|
else
|
121
121
|
path
|
@@ -223,8 +223,7 @@ class Thor
|
|
223
223
|
|
224
224
|
contents = if is_uri
|
225
225
|
require "open-uri"
|
226
|
-
|
227
|
-
URI.send(:open, path, "Accept" => "application/x-thor-template", &:read)
|
226
|
+
URI.open(path, "Accept" => "application/x-thor-template", &:read)
|
228
227
|
else
|
229
228
|
File.open(path, &:read)
|
230
229
|
end
|
@@ -285,7 +284,7 @@ class Thor
|
|
285
284
|
#
|
286
285
|
def run_ruby_script(command, config = {})
|
287
286
|
return unless behavior == :invoke
|
288
|
-
run command, config.merge(:
|
287
|
+
run command, config.merge(with: Thor::Util.ruby_command)
|
289
288
|
end
|
290
289
|
|
291
290
|
# Run a thor command. A hash of options can be given and it's converted to
|
@@ -316,7 +315,7 @@ class Thor
|
|
316
315
|
args.push Thor::Options.to_switches(config)
|
317
316
|
command = args.join(" ").strip
|
318
317
|
|
319
|
-
run command, :
|
318
|
+
run command, with: :thor, verbose: verbose, pretend: pretend, capture: capture
|
320
319
|
end
|
321
320
|
|
322
321
|
protected
|
@@ -324,7 +323,7 @@ class Thor
|
|
324
323
|
# Allow current root to be shared between invocations.
|
325
324
|
#
|
326
325
|
def _shared_configuration #:nodoc:
|
327
|
-
super.merge!(:
|
326
|
+
super.merge!(destination_root: destination_root)
|
328
327
|
end
|
329
328
|
|
330
329
|
def _cleanup_options_and_set(options, key) #:nodoc:
|
data/lib/thor/base.rb
CHANGED
@@ -24,9 +24,9 @@ class Thor
|
|
24
24
|
|
25
25
|
class << self
|
26
26
|
def deprecation_warning(message) #:nodoc:
|
27
|
-
unless ENV[
|
27
|
+
unless ENV["THOR_SILENCE_DEPRECATION"]
|
28
28
|
warn "Deprecation warning: #{message}\n" +
|
29
|
-
|
29
|
+
"You can silence deprecations warning by setting the environment variable THOR_SILENCE_DEPRECATION."
|
30
30
|
end
|
31
31
|
end
|
32
32
|
end
|
@@ -60,6 +60,7 @@ class Thor
|
|
60
60
|
|
61
61
|
command_options = config.delete(:command_options) # hook for start
|
62
62
|
parse_options = parse_options.merge(command_options) if command_options
|
63
|
+
|
63
64
|
if local_options.is_a?(Array)
|
64
65
|
array_options = local_options
|
65
66
|
hash_options = {}
|
@@ -73,9 +74,24 @@ class Thor
|
|
73
74
|
# Let Thor::Options parse the options first, so it can remove
|
74
75
|
# declared options from the array. This will leave us with
|
75
76
|
# a list of arguments that weren't declared.
|
76
|
-
|
77
|
-
|
78
|
-
|
77
|
+
current_command = config[:current_command]
|
78
|
+
stop_on_unknown = self.class.stop_on_unknown_option? current_command
|
79
|
+
|
80
|
+
# Give a relation of options.
|
81
|
+
# After parsing, Thor::Options check whether right relations are kept
|
82
|
+
relations = if current_command.nil?
|
83
|
+
{exclusive_option_names: [], at_least_one_option_names: []}
|
84
|
+
else
|
85
|
+
current_command.options_relation
|
86
|
+
end
|
87
|
+
|
88
|
+
self.class.class_exclusive_option_names.map { |n| relations[:exclusive_option_names] << n }
|
89
|
+
self.class.class_at_least_one_option_names.map { |n| relations[:at_least_one_option_names] << n }
|
90
|
+
|
91
|
+
disable_required_check = self.class.disable_required_check? current_command
|
92
|
+
|
93
|
+
opts = Thor::Options.new(parse_options, hash_options, stop_on_unknown, disable_required_check, relations)
|
94
|
+
|
79
95
|
self.options = opts.parse(array_options)
|
80
96
|
self.options = config[:class_options].merge(options) if config[:class_options]
|
81
97
|
|
@@ -310,9 +326,92 @@ class Thor
|
|
310
326
|
# :hide:: -- If you want to hide this option from the help.
|
311
327
|
#
|
312
328
|
def class_option(name, options = {})
|
329
|
+
unless [ Symbol, String ].any? { |klass| name.is_a?(klass) }
|
330
|
+
raise ArgumentError, "Expected a Symbol or String, got #{name.inspect}"
|
331
|
+
end
|
313
332
|
build_option(name, options, class_options)
|
314
333
|
end
|
315
334
|
|
335
|
+
# Adds and declares option group for exclusive options in the
|
336
|
+
# block and arguments. You can declare options as the outside of the block.
|
337
|
+
#
|
338
|
+
# ==== Parameters
|
339
|
+
# Array[Thor::Option.name]
|
340
|
+
#
|
341
|
+
# ==== Examples
|
342
|
+
#
|
343
|
+
# class_exclusive do
|
344
|
+
# class_option :one
|
345
|
+
# class_option :two
|
346
|
+
# end
|
347
|
+
#
|
348
|
+
# Or
|
349
|
+
#
|
350
|
+
# class_option :one
|
351
|
+
# class_option :two
|
352
|
+
# class_exclusive :one, :two
|
353
|
+
#
|
354
|
+
# If you give "--one" and "--two" at the same time ExclusiveArgumentsError
|
355
|
+
# will be raised.
|
356
|
+
#
|
357
|
+
def class_exclusive(*args, &block)
|
358
|
+
register_options_relation_for(:class_options,
|
359
|
+
:class_exclusive_option_names, *args, &block)
|
360
|
+
end
|
361
|
+
|
362
|
+
# Adds and declares option group for required at least one of options in the
|
363
|
+
# block and arguments. You can declare options as the outside of the block.
|
364
|
+
#
|
365
|
+
# ==== Examples
|
366
|
+
#
|
367
|
+
# class_at_least_one do
|
368
|
+
# class_option :one
|
369
|
+
# class_option :two
|
370
|
+
# end
|
371
|
+
#
|
372
|
+
# Or
|
373
|
+
#
|
374
|
+
# class_option :one
|
375
|
+
# class_option :two
|
376
|
+
# class_at_least_one :one, :two
|
377
|
+
#
|
378
|
+
# If you do not give "--one" and "--two" AtLeastOneRequiredArgumentError
|
379
|
+
# will be raised.
|
380
|
+
#
|
381
|
+
# You can use class_at_least_one and class_exclusive at the same time.
|
382
|
+
#
|
383
|
+
# class_exclusive do
|
384
|
+
# class_at_least_one do
|
385
|
+
# class_option :one
|
386
|
+
# class_option :two
|
387
|
+
# end
|
388
|
+
# end
|
389
|
+
#
|
390
|
+
# Then it is required either only one of "--one" or "--two".
|
391
|
+
#
|
392
|
+
def class_at_least_one(*args, &block)
|
393
|
+
register_options_relation_for(:class_options,
|
394
|
+
:class_at_least_one_option_names, *args, &block)
|
395
|
+
end
|
396
|
+
|
397
|
+
# Returns this class exclusive options array set, looking up in the ancestors chain.
|
398
|
+
#
|
399
|
+
# ==== Returns
|
400
|
+
# Array[Array[Thor::Option.name]]
|
401
|
+
#
|
402
|
+
def class_exclusive_option_names
|
403
|
+
@class_exclusive_option_names ||= from_superclass(:class_exclusive_option_names, [])
|
404
|
+
end
|
405
|
+
|
406
|
+
# Returns this class at least one of required options array set, looking up in the ancestors chain.
|
407
|
+
#
|
408
|
+
# ==== Returns
|
409
|
+
# Array[Array[Thor::Option.name]]
|
410
|
+
#
|
411
|
+
def class_at_least_one_option_names
|
412
|
+
@class_at_least_one_option_names ||= from_superclass(:class_at_least_one_option_names, [])
|
413
|
+
end
|
414
|
+
|
316
415
|
# Removes a previous defined argument. If :undefine is given, undefine
|
317
416
|
# accessors as well.
|
318
417
|
#
|
@@ -565,12 +664,12 @@ class Thor
|
|
565
664
|
item.push(option.description ? "# #{option.description}" : "")
|
566
665
|
|
567
666
|
list << item
|
568
|
-
list << ["", "# Default: #{option.
|
569
|
-
list << ["", "# Possible values: #{option.
|
667
|
+
list << ["", "# Default: #{option.print_default}"] if option.show_default?
|
668
|
+
list << ["", "# Possible values: #{option.enum_to_s}"] if option.enum
|
570
669
|
end
|
571
670
|
|
572
671
|
shell.say(group_name ? "#{group_name} options:" : "Options:")
|
573
|
-
shell.print_table(list, :
|
672
|
+
shell.print_table(list, indent: 2)
|
574
673
|
shell.say ""
|
575
674
|
end
|
576
675
|
|
@@ -587,7 +686,7 @@ class Thor
|
|
587
686
|
# options<Hash>:: Described in both class_option and method_option.
|
588
687
|
# scope<Hash>:: Options hash that is being built up
|
589
688
|
def build_option(name, options, scope) #:nodoc:
|
590
|
-
scope[name] = Thor::Option.new(name, {:
|
689
|
+
scope[name] = Thor::Option.new(name, {check_default_type: check_default_type}.merge!(options))
|
591
690
|
end
|
592
691
|
|
593
692
|
# Receives a hash of options, parse them and add to the scope. This is a
|
@@ -693,6 +792,34 @@ class Thor
|
|
693
792
|
def dispatch(command, given_args, given_opts, config) #:nodoc:
|
694
793
|
raise NotImplementedError
|
695
794
|
end
|
795
|
+
|
796
|
+
# Register a relation of options for target(method_option/class_option)
|
797
|
+
# by args and block.
|
798
|
+
def register_options_relation_for(target, relation, *args, &block) # :nodoc:
|
799
|
+
opt = args.pop if args.last.is_a? Hash
|
800
|
+
opt ||= {}
|
801
|
+
names = args.map{ |arg| arg.to_s }
|
802
|
+
names += built_option_names(target, opt, &block) if block_given?
|
803
|
+
command_scope_member(relation, opt) << names
|
804
|
+
end
|
805
|
+
|
806
|
+
# Get target(method_options or class_options) options
|
807
|
+
# of before and after by block evaluation.
|
808
|
+
def built_option_names(target, opt = {}, &block) # :nodoc:
|
809
|
+
before = command_scope_member(target, opt).map{ |k,v| v.name }
|
810
|
+
instance_eval(&block)
|
811
|
+
after = command_scope_member(target, opt).map{ |k,v| v.name }
|
812
|
+
after - before
|
813
|
+
end
|
814
|
+
|
815
|
+
# Get command scope member by name.
|
816
|
+
def command_scope_member(name, options = {}) # :nodoc:
|
817
|
+
if options[:for]
|
818
|
+
find_and_refresh_command(options[:for]).send(name)
|
819
|
+
else
|
820
|
+
send(name)
|
821
|
+
end
|
822
|
+
end
|
696
823
|
end
|
697
824
|
end
|
698
825
|
end
|
data/lib/thor/command.rb
CHANGED
@@ -1,14 +1,15 @@
|
|
1
1
|
class Thor
|
2
|
-
class Command < Struct.new(:name, :description, :long_description, :usage, :options, :ancestor_name)
|
2
|
+
class Command < Struct.new(:name, :description, :long_description, :wrap_long_description, :usage, :options, :options_relation, :ancestor_name)
|
3
3
|
FILE_REGEXP = /^#{Regexp.escape(File.dirname(__FILE__))}/
|
4
4
|
|
5
|
-
def initialize(name, description, long_description, usage, options = nil)
|
6
|
-
super(name.to_s, description, long_description, usage, options || {})
|
5
|
+
def initialize(name, description, long_description, wrap_long_description, usage, options = nil, options_relation = nil)
|
6
|
+
super(name.to_s, description, long_description, wrap_long_description, usage, options || {}, options_relation || {})
|
7
7
|
end
|
8
8
|
|
9
9
|
def initialize_copy(other) #:nodoc:
|
10
10
|
super(other)
|
11
11
|
self.options = other.options.dup if other.options
|
12
|
+
self.options_relation = other.options_relation.dup if other.options_relation
|
12
13
|
end
|
13
14
|
|
14
15
|
def hidden?
|
@@ -62,6 +63,14 @@ class Thor
|
|
62
63
|
end.join("\n")
|
63
64
|
end
|
64
65
|
|
66
|
+
def method_exclusive_option_names #:nodoc:
|
67
|
+
self.options_relation[:exclusive_option_names] || []
|
68
|
+
end
|
69
|
+
|
70
|
+
def method_at_least_one_option_names #:nodoc:
|
71
|
+
self.options_relation[:at_least_one_option_names] || []
|
72
|
+
end
|
73
|
+
|
65
74
|
protected
|
66
75
|
|
67
76
|
# Add usage with required arguments
|
@@ -127,7 +136,7 @@ class Thor
|
|
127
136
|
# A dynamic command that handles method missing scenarios.
|
128
137
|
class DynamicCommand < Command
|
129
138
|
def initialize(name, options = nil)
|
130
|
-
super(name.to_s, "A dynamically-generated command", name.to_s, name.to_s, options)
|
139
|
+
super(name.to_s, "A dynamically-generated command", name.to_s, nil, name.to_s, options)
|
131
140
|
end
|
132
141
|
|
133
142
|
def run(instance, args = [])
|
data/lib/thor/error.rb
CHANGED
@@ -1,26 +1,15 @@
|
|
1
1
|
class Thor
|
2
2
|
Correctable = if defined?(DidYouMean::SpellChecker) && defined?(DidYouMean::Correctable) # rubocop:disable Naming/ConstantName
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
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
|
3
|
+
Module.new do
|
4
|
+
def to_s
|
5
|
+
super + DidYouMean.formatter.message_for(corrections)
|
6
|
+
end
|
7
|
+
|
8
|
+
def corrections
|
9
|
+
@corrections ||= self.class.const_get(:SpellChecker).new(self).corrections
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
24
13
|
|
25
14
|
# Thor::Error is raised when it's caused by wrong usage of thor classes. Those
|
26
15
|
# errors have their backtrace suppressed and are nicely shown to the user.
|
@@ -45,7 +34,7 @@ class Thor
|
|
45
34
|
end
|
46
35
|
|
47
36
|
def spell_checker
|
48
|
-
|
37
|
+
DidYouMean::SpellChecker.new(dictionary: error.all_commands)
|
49
38
|
end
|
50
39
|
end
|
51
40
|
|
@@ -87,7 +76,7 @@ class Thor
|
|
87
76
|
end
|
88
77
|
|
89
78
|
def spell_checker
|
90
|
-
@spell_checker ||=
|
79
|
+
@spell_checker ||= DidYouMean::SpellChecker.new(dictionary: error.switches)
|
91
80
|
end
|
92
81
|
end
|
93
82
|
|
@@ -108,4 +97,10 @@ class Thor
|
|
108
97
|
|
109
98
|
class MalformattedArgumentError < InvocationError
|
110
99
|
end
|
100
|
+
|
101
|
+
class ExclusiveArgumentError < InvocationError
|
102
|
+
end
|
103
|
+
|
104
|
+
class AtLeastOneRequiredArgumentError < InvocationError
|
105
|
+
end
|
111
106
|
end
|
data/lib/thor/group.rb
CHANGED
@@ -211,6 +211,17 @@ class Thor::Group
|
|
211
211
|
raise error, msg
|
212
212
|
end
|
213
213
|
|
214
|
+
# Checks if a specified command exists.
|
215
|
+
#
|
216
|
+
# ==== Parameters
|
217
|
+
# command_name<String>:: The name of the command to check for existence.
|
218
|
+
#
|
219
|
+
# ==== Returns
|
220
|
+
# Boolean:: +true+ if the command exists, +false+ otherwise.
|
221
|
+
def command_exists?(command_name) #:nodoc:
|
222
|
+
commands.keys.include?(command_name)
|
223
|
+
end
|
224
|
+
|
214
225
|
protected
|
215
226
|
|
216
227
|
# The method responsible for dispatching given the args.
|