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
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,14 @@ 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(&:dump).join(" ")
|
30
|
+
else
|
31
|
+
@default
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
27
35
|
def usage
|
28
36
|
required? ? banner : "[#{banner}]"
|
29
37
|
end
|
@@ -41,11 +49,19 @@ class Thor
|
|
41
49
|
end
|
42
50
|
end
|
43
51
|
|
52
|
+
def enum_to_s
|
53
|
+
if enum.respond_to? :join
|
54
|
+
enum.join(", ")
|
55
|
+
else
|
56
|
+
"#{enum.first}..#{enum.last}"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
44
60
|
protected
|
45
61
|
|
46
62
|
def validate!
|
47
63
|
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
|
64
|
+
raise ArgumentError, "An argument cannot have an enum other than an enumerable." if @enum && !@enum.is_a?(Enumerable)
|
49
65
|
end
|
50
66
|
|
51
67
|
def valid_type?(type)
|
@@ -30,11 +30,7 @@ class Thor
|
|
30
30
|
|
31
31
|
arguments.each do |argument|
|
32
32
|
if !argument.default.nil?
|
33
|
-
|
34
|
-
@assigns[argument.human_name] = argument.default.dup
|
35
|
-
rescue TypeError # Compatibility shim for un-dup-able Fixnum in Ruby < 2.4
|
36
|
-
@assigns[argument.human_name] = argument.default
|
37
|
-
end
|
33
|
+
@assigns[argument.human_name] = argument.default.dup
|
38
34
|
elsif argument.required?
|
39
35
|
@non_assigned_required << argument
|
40
36
|
end
|
@@ -121,8 +117,18 @@ class Thor
|
|
121
117
|
#
|
122
118
|
def parse_array(name)
|
123
119
|
return shift if peek.is_a?(Array)
|
120
|
+
|
124
121
|
array = []
|
125
|
-
|
122
|
+
|
123
|
+
while current_is_value?
|
124
|
+
value = shift
|
125
|
+
|
126
|
+
if !value.empty?
|
127
|
+
validate_enum_value!(name, value, "Expected all values of '%s' to be one of %s; got %s")
|
128
|
+
end
|
129
|
+
|
130
|
+
array << value
|
131
|
+
end
|
126
132
|
array
|
127
133
|
end
|
128
134
|
|
@@ -138,11 +144,9 @@ class Thor
|
|
138
144
|
end
|
139
145
|
|
140
146
|
value = $&.index(".") ? shift.to_f : shift.to_i
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
end
|
145
|
-
end
|
147
|
+
|
148
|
+
validate_enum_value!(name, value, "Expected '%s' to be one of %s; got %s")
|
149
|
+
|
146
150
|
value
|
147
151
|
end
|
148
152
|
|
@@ -156,15 +160,27 @@ class Thor
|
|
156
160
|
nil
|
157
161
|
else
|
158
162
|
value = shift
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
end
|
163
|
-
end
|
163
|
+
|
164
|
+
validate_enum_value!(name, value, "Expected '%s' to be one of %s; got %s")
|
165
|
+
|
164
166
|
value
|
165
167
|
end
|
166
168
|
end
|
167
169
|
|
170
|
+
# Raises an error if the switch is an enum and the values aren't included on it.
|
171
|
+
#
|
172
|
+
def validate_enum_value!(name, value, message)
|
173
|
+
return unless @switches.is_a?(Hash)
|
174
|
+
|
175
|
+
switch = @switches[name]
|
176
|
+
|
177
|
+
return unless switch
|
178
|
+
|
179
|
+
if switch.enum && !switch.enum.include?(value)
|
180
|
+
raise MalformattedArgumentError, message % [name, switch.enum_to_s, value]
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
168
184
|
# Raises an error if @non_assigned_required array is not empty.
|
169
185
|
#
|
170
186
|
def check_requirement!
|
data/lib/thor/parser/option.rb
CHANGED
@@ -11,7 +11,7 @@ class Thor
|
|
11
11
|
super
|
12
12
|
@lazy_default = options[:lazy_default]
|
13
13
|
@group = options[:group].to_s.capitalize if options[:group]
|
14
|
-
@aliases =
|
14
|
+
@aliases = normalize_aliases(options[:aliases])
|
15
15
|
@hide = options[:hide]
|
16
16
|
end
|
17
17
|
|
@@ -69,7 +69,7 @@ class Thor
|
|
69
69
|
value.class.name.downcase.to_sym
|
70
70
|
end
|
71
71
|
|
72
|
-
new(name.to_s, :
|
72
|
+
new(name.to_s, required: required, type: type, default: default, aliases: aliases)
|
73
73
|
end
|
74
74
|
|
75
75
|
def switch_name
|
@@ -89,8 +89,8 @@ class Thor
|
|
89
89
|
|
90
90
|
sample = "[#{sample}]".dup unless required?
|
91
91
|
|
92
|
-
if boolean?
|
93
|
-
sample << ", [#{dasherize('no-' + human_name)}]
|
92
|
+
if boolean? && name != "force" && !name.match(/\A(no|skip)[\-_]/)
|
93
|
+
sample << ", [#{dasherize('no-' + human_name)}], [#{dasherize('skip-' + human_name)}]"
|
94
94
|
end
|
95
95
|
|
96
96
|
aliases_for_usage.ljust(padding) + sample
|
@@ -104,6 +104,15 @@ class Thor
|
|
104
104
|
end
|
105
105
|
end
|
106
106
|
|
107
|
+
def show_default?
|
108
|
+
case default
|
109
|
+
when TrueClass, FalseClass
|
110
|
+
true
|
111
|
+
else
|
112
|
+
super
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
107
116
|
VALID_TYPES.each do |type|
|
108
117
|
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
109
118
|
def #{type}?
|
@@ -142,8 +151,8 @@ class Thor
|
|
142
151
|
raise ArgumentError, err
|
143
152
|
elsif @check_default_type == nil
|
144
153
|
Thor.deprecation_warning "#{err}.\n" +
|
145
|
-
|
146
|
-
|
154
|
+
"This will be rejected in the future unless you explicitly pass the options `check_default_type: false`" +
|
155
|
+
" or call `allow_incompatible_default_type!` in your code"
|
147
156
|
end
|
148
157
|
end
|
149
158
|
end
|
@@ -159,5 +168,11 @@ class Thor
|
|
159
168
|
def dasherize(str)
|
160
169
|
(str.length > 1 ? "--" : "-") + str.tr("_", "-")
|
161
170
|
end
|
171
|
+
|
172
|
+
private
|
173
|
+
|
174
|
+
def normalize_aliases(aliases)
|
175
|
+
Array(aliases).map { |short| short.to_s.sub(/^(?!\-)/, "-") }
|
176
|
+
end
|
162
177
|
end
|
163
178
|
end
|
data/lib/thor/parser/options.rb
CHANGED
@@ -29,8 +29,10 @@ class Thor
|
|
29
29
|
#
|
30
30
|
# If +stop_on_unknown+ is true, #parse will stop as soon as it encounters
|
31
31
|
# an unknown option or a regular argument.
|
32
|
-
def initialize(hash_options = {}, defaults = {}, stop_on_unknown = false, disable_required_check = false)
|
32
|
+
def initialize(hash_options = {}, defaults = {}, stop_on_unknown = false, disable_required_check = false, relations = {})
|
33
33
|
@stop_on_unknown = stop_on_unknown
|
34
|
+
@exclusives = (relations[:exclusive_option_names] || []).select{|array| !array.empty?}
|
35
|
+
@at_least_ones = (relations[:at_least_one_option_names] || []).select{|array| !array.empty?}
|
34
36
|
@disable_required_check = disable_required_check
|
35
37
|
options = hash_options.values
|
36
38
|
super(options)
|
@@ -50,8 +52,7 @@ class Thor
|
|
50
52
|
options.each do |option|
|
51
53
|
@switches[option.switch_name] = option
|
52
54
|
|
53
|
-
option.aliases.each do |
|
54
|
-
name = short.to_s.sub(/^(?!\-)/, "-")
|
55
|
+
option.aliases.each do |name|
|
55
56
|
@shorts[name] ||= option.switch_name
|
56
57
|
end
|
57
58
|
end
|
@@ -101,7 +102,7 @@ class Thor
|
|
101
102
|
unshift($1.split("").map { |f| "-#{f}" })
|
102
103
|
next
|
103
104
|
when EQ_RE
|
104
|
-
unshift($2, :
|
105
|
+
unshift($2, is_value: true)
|
105
106
|
switch = $1
|
106
107
|
when SHORT_NUM
|
107
108
|
unshift($2)
|
@@ -132,12 +133,38 @@ class Thor
|
|
132
133
|
end
|
133
134
|
|
134
135
|
check_requirement! unless @disable_required_check
|
136
|
+
check_exclusive!
|
137
|
+
check_at_least_one!
|
135
138
|
|
136
139
|
assigns = Thor::CoreExt::HashWithIndifferentAccess.new(@assigns)
|
137
140
|
assigns.freeze
|
138
141
|
assigns
|
139
142
|
end
|
140
143
|
|
144
|
+
def check_exclusive!
|
145
|
+
opts = @assigns.keys
|
146
|
+
# When option A and B are exclusive, if A and B are given at the same time,
|
147
|
+
# the difference of argument array size will decrease.
|
148
|
+
found = @exclusives.find{ |ex| (ex - opts).size < ex.size - 1 }
|
149
|
+
if found
|
150
|
+
names = names_to_switch_names(found & opts).map{|n| "'#{n}'"}
|
151
|
+
class_name = self.class.name.split("::").last.downcase
|
152
|
+
fail ExclusiveArgumentError, "Found exclusive #{class_name} #{names.join(", ")}"
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def check_at_least_one!
|
157
|
+
opts = @assigns.keys
|
158
|
+
# When at least one is required of the options A and B,
|
159
|
+
# if the both options were not given, none? would be true.
|
160
|
+
found = @at_least_ones.find{ |one_reqs| one_reqs.none?{ |o| opts.include? o} }
|
161
|
+
if found
|
162
|
+
names = names_to_switch_names(found).map{|n| "'#{n}'"}
|
163
|
+
class_name = self.class.name.split("::").last.downcase
|
164
|
+
fail AtLeastOneRequiredArgumentError, "Not found at least one of required #{class_name} #{names.join(", ")}"
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
141
168
|
def check_unknown!
|
142
169
|
to_check = @stopped_parsing_after_extra_index ? @extra[0...@stopped_parsing_after_extra_index] : @extra
|
143
170
|
|
@@ -148,6 +175,17 @@ class Thor
|
|
148
175
|
|
149
176
|
protected
|
150
177
|
|
178
|
+
# Option names changes to swith name or human name
|
179
|
+
def names_to_switch_names(names = [])
|
180
|
+
@switches.map do |_, o|
|
181
|
+
if names.include? o.name
|
182
|
+
o.respond_to?(:switch_name) ? o.switch_name : o.human_name
|
183
|
+
else
|
184
|
+
nil
|
185
|
+
end
|
186
|
+
end.compact
|
187
|
+
end
|
188
|
+
|
151
189
|
def assign_result!(option, result)
|
152
190
|
if option.repeatable && option.type == :hash
|
153
191
|
(@assigns[option.human_name] ||= {}).merge!(result)
|
@@ -212,7 +250,8 @@ class Thor
|
|
212
250
|
@parsing_options
|
213
251
|
end
|
214
252
|
|
215
|
-
# Parse boolean values which can be given as --foo=true
|
253
|
+
# Parse boolean values which can be given as --foo=true or --foo for true values, or
|
254
|
+
# --foo=false, --no-foo or --skip-foo for false values.
|
216
255
|
#
|
217
256
|
def parse_boolean(switch)
|
218
257
|
if current_is_value?
|
data/lib/thor/runner.rb
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
require_relative "../thor"
|
2
2
|
require_relative "group"
|
3
3
|
|
4
|
-
require "yaml"
|
5
4
|
require "digest/sha2"
|
6
5
|
require "pathname"
|
7
6
|
|
@@ -23,7 +22,7 @@ class Thor::Runner < Thor #:nodoc:
|
|
23
22
|
initialize_thorfiles(meth)
|
24
23
|
klass, command = Thor::Util.find_class_and_command_by_namespace(meth)
|
25
24
|
self.class.handle_no_command_error(command, false) if klass.nil?
|
26
|
-
klass.start(["-h", command].compact, :
|
25
|
+
klass.start(["-h", command].compact, shell: shell)
|
27
26
|
else
|
28
27
|
super
|
29
28
|
end
|
@@ -38,11 +37,11 @@ class Thor::Runner < Thor #:nodoc:
|
|
38
37
|
klass, command = Thor::Util.find_class_and_command_by_namespace(meth)
|
39
38
|
self.class.handle_no_command_error(command, false) if klass.nil?
|
40
39
|
args.unshift(command) if command
|
41
|
-
klass.start(args, :
|
40
|
+
klass.start(args, shell: shell)
|
42
41
|
end
|
43
42
|
|
44
43
|
desc "install NAME", "Install an optionally named Thor file into your system commands"
|
45
|
-
method_options :
|
44
|
+
method_options as: :string, relative: :boolean, force: :boolean
|
46
45
|
def install(name) # rubocop:disable Metrics/MethodLength
|
47
46
|
initialize_thorfiles
|
48
47
|
|
@@ -53,7 +52,7 @@ class Thor::Runner < Thor #:nodoc:
|
|
53
52
|
package = :file
|
54
53
|
require "open-uri"
|
55
54
|
begin
|
56
|
-
contents = URI.
|
55
|
+
contents = URI.open(name, &:read)
|
57
56
|
rescue OpenURI::HTTPError
|
58
57
|
raise Error, "Error opening URI '#{name}'"
|
59
58
|
end
|
@@ -69,7 +68,7 @@ class Thor::Runner < Thor #:nodoc:
|
|
69
68
|
base = name
|
70
69
|
package = :file
|
71
70
|
require "open-uri"
|
72
|
-
contents = URI.
|
71
|
+
contents = URI.open(name, &:read)
|
73
72
|
end
|
74
73
|
rescue Errno::ENOENT
|
75
74
|
raise Error, "Error opening file '#{name}'"
|
@@ -101,9 +100,9 @@ class Thor::Runner < Thor #:nodoc:
|
|
101
100
|
end
|
102
101
|
|
103
102
|
thor_yaml[as] = {
|
104
|
-
:
|
105
|
-
:
|
106
|
-
:
|
103
|
+
filename: Digest::SHA256.hexdigest(name + as),
|
104
|
+
location: location,
|
105
|
+
namespaces: Thor::Util.namespaces_in_content(contents, base)
|
107
106
|
}
|
108
107
|
|
109
108
|
save_yaml(thor_yaml)
|
@@ -164,14 +163,14 @@ class Thor::Runner < Thor #:nodoc:
|
|
164
163
|
end
|
165
164
|
|
166
165
|
desc "installed", "List the installed Thor modules and commands"
|
167
|
-
method_options :
|
166
|
+
method_options internal: :boolean
|
168
167
|
def installed
|
169
168
|
initialize_thorfiles(nil, true)
|
170
169
|
display_klasses(true, options["internal"])
|
171
170
|
end
|
172
171
|
|
173
172
|
desc "list [SEARCH]", "List the available thor commands (--substring means .*SEARCH)"
|
174
|
-
method_options :
|
173
|
+
method_options substring: :boolean, group: :string, all: :boolean, debug: :boolean
|
175
174
|
def list(search = "")
|
176
175
|
initialize_thorfiles
|
177
176
|
|
@@ -195,6 +194,7 @@ private
|
|
195
194
|
def thor_yaml
|
196
195
|
@thor_yaml ||= begin
|
197
196
|
yaml_file = File.join(thor_root, "thor.yml")
|
197
|
+
require "yaml"
|
198
198
|
yaml = YAML.load_file(yaml_file) if File.exist?(yaml_file)
|
199
199
|
yaml || {}
|
200
200
|
end
|
@@ -313,7 +313,7 @@ private
|
|
313
313
|
say shell.set_color(namespace, :blue, true)
|
314
314
|
say "-" * namespace.size
|
315
315
|
|
316
|
-
print_table(list, :
|
316
|
+
print_table(list, truncate: true)
|
317
317
|
say
|
318
318
|
end
|
319
319
|
alias_method :display_tasks, :display_commands
|