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.
@@ -143,7 +143,7 @@ class Thor
143
143
 
144
144
  # Configuration values that are shared between invocations.
145
145
  def _shared_configuration #:nodoc:
146
- {:invocations => @_invocations}
146
+ {invocations: @_invocations}
147
147
  end
148
148
 
149
149
  # This method simply retrieves the class and command to be invoked.
@@ -13,10 +13,10 @@ class Thor
13
13
  end
14
14
 
15
15
  def entered?
16
- @depth > 0
16
+ @depth.positive?
17
17
  end
18
18
 
19
- private
19
+ private
20
20
 
21
21
  def push
22
22
  @depth += 1
@@ -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 array." if @enum && !@enum.is_a?(Array)
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
- begin
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
- array << shift while current_is_value?
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
- if @switches.is_a?(Hash) && switch = @switches[name]
142
- if switch.enum && !switch.enum.include?(value)
143
- raise MalformattedArgumentError, "Expected '#{name}' to be one of #{switch.enum.join(', ')}; got #{value}"
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
- if @switches.is_a?(Hash) && switch = @switches[name]
160
- if switch.enum && !switch.enum.include?(value)
161
- raise MalformattedArgumentError, "Expected '#{name}' to be one of #{switch.enum.join(', ')}; got #{value}"
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!
@@ -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 = Array(options[: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, :required => required, :type => type, :default => default, :aliases => aliases)
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)}]" unless (name == "force") || name.start_with?("no-")
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
- 'This will be rejected in the future unless you explicitly pass the options `check_default_type: false`' +
146
- ' or call `allow_incompatible_default_type!` in your code'
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
@@ -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 |short|
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, :is_value => true)
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, --foo or --no-foo.
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, :shell => shell)
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, :shell => shell)
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 :as => :string, :relative => :boolean, :force => :boolean
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.send(:open, name, &:read) # Using `send` for Ruby 2.4- support
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.send(:open, name, &:read) # for ruby 2.1-2.4
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
- :filename => Digest::SHA256.hexdigest(name + as),
105
- :location => location,
106
- :namespaces => Thor::Util.namespaces_in_content(contents, base)
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 :internal => :boolean
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 :substring => :boolean, :group => :string, :all => :boolean, :debug => :boolean
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, :truncate => true)
316
+ print_table(list, truncate: true)
317
317
  say
318
318
  end
319
319
  alias_method :display_tasks, :display_commands