thor 1.2.1 → 1.3.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/lib/thor/actions/create_file.rb +3 -2
- data/lib/thor/actions/directory.rb +1 -1
- data/lib/thor/actions/empty_directory.rb +1 -1
- data/lib/thor/actions/file_manipulation.rb +8 -10
- data/lib/thor/actions/inject_into_file.rb +15 -4
- data/lib/thor/actions.rb +15 -15
- data/lib/thor/base.rb +140 -14
- data/lib/thor/command.rb +13 -4
- data/lib/thor/core_ext/hash_with_indifferent_access.rb +4 -0
- data/lib/thor/error.rb +16 -25
- data/lib/thor/group.rb +1 -1
- data/lib/thor/invocation.rb +1 -1
- data/lib/thor/nested_context.rb +2 -2
- data/lib/thor/parser/argument.rb +20 -1
- data/lib/thor/parser/arguments.rb +33 -17
- data/lib/thor/parser/option.rb +27 -8
- data/lib/thor/parser/options.rb +44 -6
- data/lib/thor/rake_compat.rb +2 -2
- data/lib/thor/runner.rb +40 -30
- data/lib/thor/shell/basic.rb +26 -150
- data/lib/thor/shell/color.rb +4 -46
- data/lib/thor/shell/column_printer.rb +29 -0
- data/lib/thor/shell/html.rb +3 -45
- data/lib/thor/shell/lcs_diff.rb +49 -0
- data/lib/thor/shell/table_printer.rb +134 -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 +8 -7
- data/lib/thor/version.rb +1 -1
- data/lib/thor.rb +155 -8
- data/thor.gemspec +14 -10
- metadata +11 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f24a2dd00001feb1d7f9d68b1cf8aff06dd9f347e230846eda8662595a514ac9
|
|
4
|
+
data.tar.gz: 5fcf2e12c1a1f7112b72286f3eded990dc6f00df0ecc45a5e32465915c10b515
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 03d0bd2991357425d2ceeeaf3a538f280e9a1a2ce7d35cc210c5413744940f022602339ce721aff4bad5c51ebde0a071d533a06c7c697dee8fabbcb468671dc5
|
|
7
|
+
data.tar.gz: 1fe2bbce8f427aaa3a9a314b647796927d8dbaf52b2873c1def2d072c3fd98076abcbc50890d913f195a30835208f5f2d272d59969a89e3821f1ff6c1080d073
|
|
@@ -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.
|
|
@@ -60,7 +61,7 @@ class Thor
|
|
|
60
61
|
invoke_with_conflict_check do
|
|
61
62
|
require "fileutils"
|
|
62
63
|
FileUtils.mkdir_p(File.dirname(destination))
|
|
63
|
-
File.open(destination, "wb") { |f| f.write render }
|
|
64
|
+
File.open(destination, "wb", config[:perm]) { |f| f.write render }
|
|
64
65
|
end
|
|
65
66
|
given_destination
|
|
66
67
|
end
|
|
@@ -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!
|
|
@@ -66,12 +66,15 @@ class Thor
|
|
|
66
66
|
# ==== Parameters
|
|
67
67
|
# source<String>:: the address of the given content.
|
|
68
68
|
# destination<String>:: the relative path to the destination root.
|
|
69
|
-
# config<Hash>:: give :verbose => false to not log the status
|
|
69
|
+
# config<Hash>:: give :verbose => false to not log the status, and
|
|
70
|
+
# :http_headers => <Hash> to add headers to an http request.
|
|
70
71
|
#
|
|
71
72
|
# ==== Examples
|
|
72
73
|
#
|
|
73
74
|
# get "http://gist.github.com/103208", "doc/README"
|
|
74
75
|
#
|
|
76
|
+
# get "http://gist.github.com/103208", "doc/README", :http_headers => {"Content-Type" => "application/json"}
|
|
77
|
+
#
|
|
75
78
|
# get "http://gist.github.com/103208" do |content|
|
|
76
79
|
# content.split("\n").first
|
|
77
80
|
# end
|
|
@@ -82,10 +85,10 @@ class Thor
|
|
|
82
85
|
|
|
83
86
|
render = if source =~ %r{^https?\://}
|
|
84
87
|
require "open-uri"
|
|
85
|
-
URI.send(:open, source) { |input| input.binmode.read }
|
|
88
|
+
URI.send(:open, source, config.fetch(:http_headers, {})) { |input| input.binmode.read }
|
|
86
89
|
else
|
|
87
90
|
source = File.expand_path(find_in_source_paths(source.to_s))
|
|
88
|
-
open(source) { |input| input.binmode.read }
|
|
91
|
+
File.open(source) { |input| input.binmode.read }
|
|
89
92
|
end
|
|
90
93
|
|
|
91
94
|
destination ||= if block_given?
|
|
@@ -120,12 +123,7 @@ class Thor
|
|
|
120
123
|
context = config.delete(:context) || instance_eval("binding")
|
|
121
124
|
|
|
122
125
|
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
|
|
126
|
+
capturable_erb = CapturableERB.new(::File.binread(source), trim_mode: "-", eoutvar: "@output_buffer")
|
|
129
127
|
content = capturable_erb.tap do |erb|
|
|
130
128
|
erb.filename = source
|
|
131
129
|
end.result(context)
|
|
@@ -252,7 +250,7 @@ class Thor
|
|
|
252
250
|
# flag<Regexp|String>:: the regexp or string to be replaced
|
|
253
251
|
# replacement<String>:: the replacement, can be also given as a block
|
|
254
252
|
# config<Hash>:: give :verbose => false to not log the status, and
|
|
255
|
-
# :force => true, to force the replacement
|
|
253
|
+
# :force => true, to force the replacement regardless of runner behavior.
|
|
256
254
|
#
|
|
257
255
|
# ==== Example
|
|
258
256
|
#
|
|
@@ -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
|
|
@@ -175,7 +175,7 @@ class Thor
|
|
|
175
175
|
shell.padding += 1 if verbose
|
|
176
176
|
@destination_stack.push File.expand_path(dir, destination_root)
|
|
177
177
|
|
|
178
|
-
# If the directory
|
|
178
|
+
# If the directory doesn't exist and we're not pretending
|
|
179
179
|
if !File.exist?(destination_root) && !pretend
|
|
180
180
|
require "fileutils"
|
|
181
181
|
FileUtils.mkdir_p(destination_root)
|
|
@@ -225,7 +225,7 @@ class Thor
|
|
|
225
225
|
require "open-uri"
|
|
226
226
|
URI.open(path, "Accept" => "application/x-thor-template", &:read)
|
|
227
227
|
else
|
|
228
|
-
open(path, &:read)
|
|
228
|
+
File.open(path, &:read)
|
|
229
229
|
end
|
|
230
230
|
|
|
231
231
|
instance_eval(contents, path)
|
|
@@ -284,7 +284,7 @@ class Thor
|
|
|
284
284
|
#
|
|
285
285
|
def run_ruby_script(command, config = {})
|
|
286
286
|
return unless behavior == :invoke
|
|
287
|
-
run command, config.merge(:
|
|
287
|
+
run command, config.merge(with: Thor::Util.ruby_command)
|
|
288
288
|
end
|
|
289
289
|
|
|
290
290
|
# Run a thor command. A hash of options can be given and it's converted to
|
|
@@ -315,7 +315,7 @@ class Thor
|
|
|
315
315
|
args.push Thor::Options.to_switches(config)
|
|
316
316
|
command = args.join(" ").strip
|
|
317
317
|
|
|
318
|
-
run command, :
|
|
318
|
+
run command, with: :thor, verbose: verbose, pretend: pretend, capture: capture
|
|
319
319
|
end
|
|
320
320
|
|
|
321
321
|
protected
|
|
@@ -323,7 +323,7 @@ class Thor
|
|
|
323
323
|
# Allow current root to be shared between invocations.
|
|
324
324
|
#
|
|
325
325
|
def _shared_configuration #:nodoc:
|
|
326
|
-
super.merge!(:
|
|
326
|
+
super.merge!(destination_root: destination_root)
|
|
327
327
|
end
|
|
328
328
|
|
|
329
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
|
#
|
|
@@ -506,7 +605,7 @@ class Thor
|
|
|
506
605
|
#
|
|
507
606
|
def public_command(*names)
|
|
508
607
|
names.each do |name|
|
|
509
|
-
class_eval "def #{name}(*); super end"
|
|
608
|
+
class_eval "def #{name}(*); super end", __FILE__, __LINE__
|
|
510
609
|
end
|
|
511
610
|
end
|
|
512
611
|
alias_method :public_task, :public_command
|
|
@@ -558,20 +657,19 @@ class Thor
|
|
|
558
657
|
return if options.empty?
|
|
559
658
|
|
|
560
659
|
list = []
|
|
561
|
-
padding = options.map { |o| o.
|
|
562
|
-
|
|
660
|
+
padding = options.map { |o| o.aliases_for_usage.size }.max.to_i
|
|
563
661
|
options.each do |option|
|
|
564
662
|
next if option.hide
|
|
565
663
|
item = [option.usage(padding)]
|
|
566
664
|
item.push(option.description ? "# #{option.description}" : "")
|
|
567
665
|
|
|
568
666
|
list << item
|
|
569
|
-
list << ["", "# Default: #{option.
|
|
570
|
-
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
|
|
571
669
|
end
|
|
572
670
|
|
|
573
671
|
shell.say(group_name ? "#{group_name} options:" : "Options:")
|
|
574
|
-
shell.print_table(list, :
|
|
672
|
+
shell.print_table(list, indent: 2)
|
|
575
673
|
shell.say ""
|
|
576
674
|
end
|
|
577
675
|
|
|
@@ -588,7 +686,7 @@ class Thor
|
|
|
588
686
|
# options<Hash>:: Described in both class_option and method_option.
|
|
589
687
|
# scope<Hash>:: Options hash that is being built up
|
|
590
688
|
def build_option(name, options, scope) #:nodoc:
|
|
591
|
-
scope[name] = Thor::Option.new(name, {:
|
|
689
|
+
scope[name] = Thor::Option.new(name, {check_default_type: check_default_type}.merge!(options))
|
|
592
690
|
end
|
|
593
691
|
|
|
594
692
|
# Receives a hash of options, parse them and add to the scope. This is a
|
|
@@ -610,7 +708,7 @@ class Thor
|
|
|
610
708
|
def find_and_refresh_command(name) #:nodoc:
|
|
611
709
|
if commands[name.to_s]
|
|
612
710
|
commands[name.to_s]
|
|
613
|
-
elsif command = all_commands[name.to_s] # rubocop:disable AssignmentInCondition
|
|
711
|
+
elsif command = all_commands[name.to_s] # rubocop:disable Lint/AssignmentInCondition
|
|
614
712
|
commands[name.to_s] = command.clone
|
|
615
713
|
else
|
|
616
714
|
raise ArgumentError, "You supplied :for => #{name.inspect}, but the command #{name.inspect} could not be found."
|
|
@@ -618,7 +716,7 @@ class Thor
|
|
|
618
716
|
end
|
|
619
717
|
alias_method :find_and_refresh_task, :find_and_refresh_command
|
|
620
718
|
|
|
621
|
-
#
|
|
719
|
+
# Every time someone inherits from a Thor class, register the klass
|
|
622
720
|
# and file into baseclass.
|
|
623
721
|
def inherited(klass)
|
|
624
722
|
super(klass)
|
|
@@ -694,6 +792,34 @@ class Thor
|
|
|
694
792
|
def dispatch(command, given_args, given_opts, config) #:nodoc:
|
|
695
793
|
raise NotImplementedError
|
|
696
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
|
|
697
823
|
end
|
|
698
824
|
end
|
|
699
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,18 +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
|
-
DidYouMean::Correctable
|
|
15
|
-
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
|
|
16
13
|
|
|
17
14
|
# Thor::Error is raised when it's caused by wrong usage of thor classes. Those
|
|
18
15
|
# errors have their backtrace suppressed and are nicely shown to the user.
|
|
@@ -37,7 +34,7 @@ class Thor
|
|
|
37
34
|
end
|
|
38
35
|
|
|
39
36
|
def spell_checker
|
|
40
|
-
|
|
37
|
+
DidYouMean::SpellChecker.new(dictionary: error.all_commands)
|
|
41
38
|
end
|
|
42
39
|
end
|
|
43
40
|
|
|
@@ -79,7 +76,7 @@ class Thor
|
|
|
79
76
|
end
|
|
80
77
|
|
|
81
78
|
def spell_checker
|
|
82
|
-
@spell_checker ||=
|
|
79
|
+
@spell_checker ||= DidYouMean::SpellChecker.new(dictionary: error.switches)
|
|
83
80
|
end
|
|
84
81
|
end
|
|
85
82
|
|
|
@@ -101,15 +98,9 @@ class Thor
|
|
|
101
98
|
class MalformattedArgumentError < InvocationError
|
|
102
99
|
end
|
|
103
100
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
else
|
|
109
|
-
DidYouMean::SPELL_CHECKERS.merge!(
|
|
110
|
-
'Thor::UndefinedCommandError' => UndefinedCommandError::SpellChecker,
|
|
111
|
-
'Thor::UnknownArgumentError' => UnknownArgumentError::SpellChecker
|
|
112
|
-
)
|
|
113
|
-
end
|
|
101
|
+
class ExclusiveArgumentError < InvocationError
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
class AtLeastOneRequiredArgumentError < InvocationError
|
|
114
105
|
end
|
|
115
106
|
end
|
data/lib/thor/group.rb
CHANGED
|
@@ -169,7 +169,7 @@ class Thor::Group
|
|
|
169
169
|
# options are added to group_options hash. Options that already exists
|
|
170
170
|
# in base_options are not added twice.
|
|
171
171
|
#
|
|
172
|
-
def get_options_from_invocations(group_options, base_options) #:nodoc:
|
|
172
|
+
def get_options_from_invocations(group_options, base_options) #:nodoc:
|
|
173
173
|
invocations.each do |name, from_option|
|
|
174
174
|
value = if from_option
|
|
175
175
|
option = class_options[name]
|
data/lib/thor/invocation.rb
CHANGED
|
@@ -143,7 +143,7 @@ class Thor
|
|
|
143
143
|
|
|
144
144
|
# Configuration values that are shared between invocations.
|
|
145
145
|
def _shared_configuration #:nodoc:
|
|
146
|
-
{:
|
|
146
|
+
{invocations: @_invocations}
|
|
147
147
|
end
|
|
148
148
|
|
|
149
149
|
# This method simply retrieves the class and command to be invoked.
|
data/lib/thor/nested_context.rb
CHANGED
data/lib/thor/parser/argument.rb
CHANGED
|
@@ -24,6 +24,17 @@ class Thor
|
|
|
24
24
|
validate! # Trigger specific validations
|
|
25
25
|
end
|
|
26
26
|
|
|
27
|
+
def print_default
|
|
28
|
+
if @type == :array and @default.is_a?(Array)
|
|
29
|
+
@default.map { |x|
|
|
30
|
+
p = x.gsub('"','\\"')
|
|
31
|
+
"\"#{p}\""
|
|
32
|
+
}.join(" ")
|
|
33
|
+
else
|
|
34
|
+
@default
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
27
38
|
def usage
|
|
28
39
|
required? ? banner : "[#{banner}]"
|
|
29
40
|
end
|
|
@@ -41,11 +52,19 @@ class Thor
|
|
|
41
52
|
end
|
|
42
53
|
end
|
|
43
54
|
|
|
55
|
+
def enum_to_s
|
|
56
|
+
if enum.respond_to? :join
|
|
57
|
+
enum.join(", ")
|
|
58
|
+
else
|
|
59
|
+
"#{enum.first}..#{enum.last}"
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
44
63
|
protected
|
|
45
64
|
|
|
46
65
|
def validate!
|
|
47
66
|
raise ArgumentError, "An argument cannot be required and have default value." if required? && !default.nil?
|
|
48
|
-
raise ArgumentError, "An argument cannot have an enum other than an
|
|
67
|
+
raise ArgumentError, "An argument cannot have an enum other than an enumerable." if @enum && !@enum.is_a?(Enumerable)
|
|
49
68
|
end
|
|
50
69
|
|
|
51
70
|
def valid_type?(type)
|