atli 0.1.4 → 0.1.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -23,39 +23,102 @@ class Thor
23
23
  def hidden?
24
24
  false
25
25
  end
26
-
26
+
27
+
28
+ # Run a command by calling the actual method on the {Thor::Base} instance.
29
+ #
27
30
  # By default, a command invokes a method in the thor class. You can change
28
31
  # this implementation to create custom commands.
29
- def run(instance, args = [])
30
- logger.trace "Command#run",
31
- self: self,
32
- instance: instance,
32
+ #
33
+ # @param [Thor::Base] instance
34
+ # Thor class instance the command is being run for.
35
+ #
36
+ # @param [Array<?>] args
37
+ # Arguments for the command.
38
+ #
39
+ # @return
40
+ # The return value of the command method on `instance`.
41
+ #
42
+ # @raise [Thor::InvocationError]
43
+ # 1. When we find a suitable method on `instance` to call, but it
44
+ # raised and {ArgumentError} **and** {#handle_argument_error?}
45
+ # returned `true`.
46
+ #
47
+ def run instance, args = []
48
+ logger.debug "Command#run",
49
+ name: self.name,
33
50
  args: args
34
51
 
52
+ # raise "BAD!!!" unless args.include? '--'
53
+
54
+ # Declaration for arity of the method, which is set in (2) below and
55
+ # used when handling raised {ArgumentError}
35
56
  arity = nil
36
-
37
- if private_method?(instance)
38
- instance.class.handle_no_command_error(name)
39
- elsif public_method?(instance)
40
- arity = instance.method(name).arity
41
- instance.__send__(name, *args)
42
- elsif local_method?(instance, :method_missing)
43
- instance.__send__(:method_missing, name.to_sym, *args)
57
+
58
+ # Method invocation switch - figure out how to make the method call to
59
+ # `instance`, or error out.
60
+ #
61
+ # Cases:
62
+ #
63
+ # 1. Protect from calling private methods by error'ing out if {#name}
64
+ # is the name of a private method of `instance`.
65
+ #
66
+ if private_method? instance
67
+ instance.class.handle_no_command_error name
68
+
69
+ # 2. The success case - if {#name} is a public method of `instance`
70
+ # than call it with `args`.
71
+ #
72
+ elsif public_method? instance
73
+ # Save the arity to use when handling {ArgumentError} below
74
+ #
75
+ # TODO Why does it fetch the {Method} *then* use {#__send__} instead
76
+ # of just `#call` it?
77
+ #
78
+ arity = instance.method( name ).arity
79
+
80
+ # Unless the method is a subcommand, remove any '--' separators
81
+ # since we know we're done option parsin'
82
+ unless subcommand? instance, name
83
+ args = args.reject { |arg| arg == '--' }
84
+ end
85
+
86
+ # Do that call
87
+ instance.__send__ name, *args
88
+
89
+ # 3. If the {Thor} instance has a `#method_missing` defined in *itself*
90
+ # (not any super class) than call that.
91
+ #
92
+ elsif local_method? instance, :method_missing
93
+ instance.__send__ :method_missing, name.to_sym, *args
94
+
95
+ # 4. We got nothing... pass of to
96
+ # {Thor::Base::ClassMethods.handle_no_command_error}
97
+ # which will raise.
98
+ #
44
99
  else
45
- instance.class.handle_no_command_error(name)
46
- end
47
- rescue ArgumentError => e
48
- if handle_argument_error?(instance, e, caller)
49
- instance.class.handle_argument_error(self, e, args, arity)
100
+ instance.class.handle_no_command_error name
101
+
102
+ end # Method invocation switch
103
+
104
+ rescue ArgumentError => error
105
+ if handle_argument_error? instance, error, caller
106
+ # NOTE I *believe* `arity` could still be `nil`, assuming that
107
+ # (3) could raise {ArgumentError} and end up here.
108
+ #
109
+ # However...
110
+ instance.class.handle_argument_error self, error, args, arity
50
111
  else
51
- raise e
112
+ raise error
52
113
  end
53
- rescue NoMethodError => e
54
- if handle_no_method_error?(instance, e, caller)
55
- instance.class.handle_no_command_error(name)
114
+
115
+ rescue NoMethodError => error
116
+ if handle_no_method_error? instance, error, caller
117
+ instance.class.handle_no_command_error name
56
118
  else
57
- raise e
119
+ raise error
58
120
  end
121
+
59
122
  rescue Exception => error
60
123
  # NOTE Need to use `#__send__` because the instance may define a
61
124
  # command (method) `#send` - and one of the test fixtures **does**:
@@ -73,7 +136,8 @@ class Thor
73
136
 
74
137
  # If you want something done right...
75
138
  raise error
76
- end
139
+ end # #run
140
+
77
141
 
78
142
  # Returns the formatted usage by injecting given required arguments
79
143
  # and required options into the given usage.
@@ -125,22 +189,76 @@ class Thor
125
189
  sort.
126
190
  join(" ")
127
191
  end
128
-
129
- # Given a target, checks if this class name is a public method.
192
+
193
+
194
+
195
+ # Is `name` a subcommand of `instance`?
196
+ #
197
+ # @param [Thor::Base] instance
198
+ # The Thor instance this command is being run for.
199
+ #
200
+ # @param [Symbol | String] name
201
+ # The subcommand / method name.
202
+ #
203
+ # @return [return_type]
204
+ # @todo Document return value.
205
+ #
206
+ def subcommand? instance, name
207
+ # It doesn't look like {Thor::Group} has `.subcommands`, so test for
208
+ # that first.
209
+ return false unless instance.class.respond_to?( :subcommands )
210
+
211
+ # See if the names is in the subcommands
212
+ instance.class.subcommands.include? name.to_s
213
+ end # #subcommand?
214
+
215
+
216
+
217
+ # Is this command's {#name} a public method of `instance`?
218
+ #
219
+ # @param [Thor::Base] instance
220
+ # The Thor instance this command is being run for.
221
+ #
222
+ # @return [Boolean]
223
+ # `true` if {#name} is a public method of `instance`.
224
+ #
130
225
  def public_method?(instance) #:nodoc:
131
226
  !(instance.public_methods & [name.to_s, name.to_sym]).empty?
132
227
  end
133
-
228
+
229
+
230
+ # Is this command's {#name} a private method of `instance`?
231
+ #
232
+ # @param [Thor::Base] instance
233
+ # The Thor instance this command is being run for.
234
+ #
235
+ # @return [Boolean]
236
+ # `true` if {#name} is a private method of `instance`.
237
+ #
134
238
  def private_method?(instance)
135
239
  !(instance.private_methods & [name.to_s, name.to_sym]).empty?
136
240
  end
137
-
241
+
242
+
243
+ # Is `name` the name of a method defined in `instance` itself (not
244
+ # any super class)?
245
+ #
246
+ # @param [Thor::Base] instance
247
+ # The Thor instance this command is being run for.
248
+ #
249
+ # @param [Symbol | String] name
250
+ # The method name.
251
+ #
252
+ # @return [Boolean]
253
+ # `true` if `name` is the name of a method defined in `instance` itself.
254
+ #
138
255
  def local_method?(instance, name)
139
256
  methods = instance.public_methods(false) +
140
257
  instance.private_methods(false) +
141
258
  instance.protected_methods(false)
142
259
  !(methods & [name.to_s, name.to_sym]).empty?
143
260
  end
261
+
144
262
 
145
263
  def sans_backtrace(backtrace, caller) #:nodoc:
146
264
  saned = backtrace.reject { |frame|
@@ -203,7 +203,7 @@ class Thor::Group
203
203
  [item]
204
204
  end
205
205
  alias_method :printable_tasks, :printable_commands
206
-
206
+
207
207
  def handle_argument_error(command, error, _args, arity) #:nodoc:
208
208
  msg = "#{basename} #{command.name} takes #{arity} argument".dup
209
209
  msg << "s" if arity > 1
@@ -211,71 +211,78 @@ class Thor::Group
211
211
  raise error, msg
212
212
  end
213
213
 
214
- protected
215
-
216
- # The method responsible for dispatching given the args.
217
- def dispatch(command, given_args, given_opts, config) #:nodoc:
218
- if Thor::HELP_MAPPINGS.include?(given_args.first)
219
- help(config[:shell])
220
- return
221
- end
214
+ protected
215
+ # ============================================================================
216
+
217
+ # The method responsible for dispatching given the args.
218
+ def dispatch(command, given_args, given_opts, config) #:nodoc:
219
+ if Thor::HELP_MAPPINGS.include?(given_args.first)
220
+ help(config[:shell])
221
+ return
222
+ end
222
223
 
223
- args, opts = Thor::Options.split(given_args)
224
- opts = given_opts || opts
224
+ args, opts = Thor::Options.split(given_args)
225
+ opts = given_opts || opts
225
226
 
226
- instance = new(args, opts, config)
227
- yield instance if block_given?
227
+ instance = new(args, opts, config)
228
+ yield instance if block_given?
228
229
 
229
- if command
230
- instance.invoke_command(all_commands[command])
231
- else
232
- instance.invoke_all
230
+ if command
231
+ instance.invoke_command(all_commands[command])
232
+ else
233
+ instance.invoke_all
234
+ end
233
235
  end
234
- end
235
236
 
236
- # The banner for this class. You can customize it if you are invoking the
237
- # thor class by another ways which is not the Thor::Runner.
238
- def banner
239
- "#{basename} #{self_command.formatted_usage(self, false)}"
240
- end
237
+ # The banner for this class. You can customize it if you are invoking the
238
+ # thor class by another ways which is not the Thor::Runner.
239
+ def banner
240
+ "#{basename} #{self_command.formatted_usage(self, false)}"
241
+ end
241
242
 
242
- # Represents the whole class as a command.
243
- def self_command #:nodoc:
244
- Thor::DynamicCommand.new(namespace, class_options)
245
- end
246
- alias_method :self_task, :self_command
243
+ # Represents the whole class as a command.
244
+ def self_command #:nodoc:
245
+ Thor::DynamicCommand.new(namespace, class_options)
246
+ end
247
+ alias_method :self_task, :self_command
247
248
 
248
- def baseclass #:nodoc:
249
- Thor::Group
250
- end
249
+ def baseclass #:nodoc:
250
+ Thor::Group
251
+ end
251
252
 
252
- def create_command(meth) #:nodoc:
253
- commands[meth.to_s] = Thor::Command.new(meth, nil, nil, nil, nil)
254
- true
255
- end
256
- alias_method :create_task, :create_command
257
- end
253
+ def create_command(meth) #:nodoc:
254
+ commands[meth.to_s] = Thor::Command.new(meth, nil, nil, nil, nil)
255
+ true
256
+ end
257
+ alias_method :create_task, :create_command
258
+
259
+ public # end protected ***************************************************
260
+
261
+ end # class << self ********************************************************
258
262
 
259
263
  include Thor::Base
260
264
 
261
- protected
262
-
263
- # Shortcut to invoke with padding and block handling. Use internally by
264
- # invoke and invoke_from_option class methods.
265
- def _invoke_for_class_method(klass, command = nil, *args, &block) #:nodoc:
266
- with_padding do
267
- if block
268
- case block.arity
269
- when 3
270
- yield(self, klass, command)
271
- when 2
272
- yield(self, klass)
273
- when 1
274
- instance_exec(klass, &block)
265
+ protected
266
+ # ==========================================================================
267
+
268
+ # Shortcut to invoke with padding and block handling. Use internally by
269
+ # invoke and invoke_from_option class methods.
270
+ def _invoke_for_class_method(klass, command = nil, *args, &block) #:nodoc:
271
+ with_padding do
272
+ if block
273
+ case block.arity
274
+ when 3
275
+ yield(self, klass, command)
276
+ when 2
277
+ yield(self, klass)
278
+ when 1
279
+ instance_exec(klass, &block)
280
+ end
281
+ else
282
+ invoke klass, command, *args
275
283
  end
276
- else
277
- invoke klass, command, *args
278
284
  end
279
285
  end
280
- end
286
+
287
+ public # end protected *****************************************************
281
288
  end
@@ -44,8 +44,15 @@ class Thor
44
44
  protected
45
45
 
46
46
  def validate!
47
- 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)
47
+ if required? && !default.nil?
48
+ raise ArgumentError,
49
+ "An argument cannot be required and have default value."
50
+ end
51
+
52
+ if @enum && !@enum.is_a?(Array)
53
+ raise ArgumentError,
54
+ "An argument cannot have an enum other than an array."
55
+ end
49
56
  end
50
57
 
51
58
  def valid_type?(type)
@@ -1,5 +1,7 @@
1
1
  class Thor
2
- class Arguments #:nodoc: # rubocop:disable ClassLength
2
+ class Arguments # rubocop:disable ClassLength
3
+ include SemanticLogger::Loggable
4
+
3
5
  NUMERIC = /[-+]?(\d*\.\d+|\d+)/
4
6
 
5
7
  # Receives an array of args and returns two arrays, one with arguments
@@ -36,140 +38,170 @@ class Thor
36
38
  end
37
39
  end
38
40
  end
41
+
39
42
 
40
- def parse(args)
43
+ def parse args
44
+ logger.debug __method__.to_s,
45
+ args: args
46
+
41
47
  @pile = args.dup
42
48
 
43
49
  @switches.each do |argument|
44
50
  break unless peek
45
51
  @non_assigned_required.delete(argument)
46
- @assigns[argument.human_name] = send(:"parse_#{argument.type}", argument.human_name)
52
+ @assigns[argument.human_name] = send :"parse_#{argument.type}",
53
+ argument.human_name
47
54
  end
48
55
 
49
56
  check_requirement!
50
57
  @assigns
51
58
  end
59
+
52
60
 
53
61
  def remaining
54
62
  @pile
55
63
  end
64
+
65
+
66
+ private # Instance Methods
67
+ # ========================================================================
56
68
 
57
- private
58
-
59
- def no_or_skip?(arg)
60
- arg =~ /^--(no|skip)-([-\w]+)$/
61
- $2
62
- end
63
-
64
- def last?
65
- @pile.empty?
66
- end
67
-
68
- def peek
69
- @pile.first
70
- end
71
-
72
- def shift
73
- @pile.shift
74
- end
75
-
76
- def unshift(arg)
77
- if arg.is_a?(Array)
78
- @pile = arg + @pile
79
- else
80
- @pile.unshift(arg)
69
+ def no_or_skip?(arg)
70
+ arg =~ /^--(no|skip)-([-\w]+)$/
71
+ $2
81
72
  end
82
- end
83
-
84
- def current_is_value?
85
- peek && peek.to_s !~ /^-/
86
- end
87
-
88
- # Runs through the argument array getting strings that contains ":" and
89
- # mark it as a hash:
90
- #
91
- # [ "name:string", "age:integer" ]
92
- #
93
- # Becomes:
94
- #
95
- # { "name" => "string", "age" => "integer" }
96
- #
97
- def parse_hash(name)
98
- return shift if peek.is_a?(Hash)
99
- hash = {}
100
-
101
- while current_is_value? && peek.include?(":")
102
- key, value = shift.split(":", 2)
103
- raise MalformattedArgumentError, "You can't specify '#{key}' more than once in option '#{name}'; got #{key}:#{hash[key]} and #{key}:#{value}" if hash.include? key
104
- hash[key] = value
73
+
74
+
75
+ def last?
76
+ @pile.empty?
105
77
  end
106
- hash
107
- end
108
-
109
- # Runs through the argument array getting all strings until no string is
110
- # found or a switch is found.
111
- #
112
- # ["a", "b", "c"]
113
- #
114
- # And returns it as an array:
115
- #
116
- # ["a", "b", "c"]
117
- #
118
- def parse_array(name)
119
- return shift if peek.is_a?(Array)
120
- array = []
121
- array << shift while current_is_value?
122
- array
123
- end
124
-
125
- # Check if the peek is numeric format and return a Float or Integer.
126
- # Check if the peek is included in enum if enum is provided.
127
- # Otherwise raises an error.
128
- #
129
- def parse_numeric(name)
130
- return shift if peek.is_a?(Numeric)
131
-
132
- unless peek =~ NUMERIC && $& == peek
133
- raise MalformattedArgumentError, "Expected numeric value for '#{name}'; got #{peek.inspect}"
78
+
79
+
80
+ def peek
81
+ @pile.first
134
82
  end
135
-
136
- value = $&.index(".") ? shift.to_f : shift.to_i
137
- if @switches.is_a?(Hash) && switch = @switches[name]
138
- if switch.enum && !switch.enum.include?(value)
139
- raise MalformattedArgumentError, "Expected '#{name}' to be one of #{switch.enum.join(', ')}; got #{value}"
83
+
84
+
85
+ def shift
86
+ @pile.shift
87
+ end
88
+
89
+
90
+ def unshift(arg)
91
+ if arg.is_a?(Array)
92
+ @pile = arg + @pile
93
+ else
94
+ @pile.unshift(arg)
140
95
  end
141
96
  end
142
- value
143
- end
97
+
98
+
99
+ def current_is_value?
100
+ peek && peek.to_s !~ /^-/
101
+ end
102
+
103
+
104
+ # Runs through the argument array getting strings that contains ":" and
105
+ # mark it as a hash:
106
+ #
107
+ # [ "name:string", "age:integer" ]
108
+ #
109
+ # Becomes:
110
+ #
111
+ # { "name" => "string", "age" => "integer" }
112
+ #
113
+ def parse_hash(name)
114
+ return shift if peek.is_a?(Hash)
115
+ hash = {}
116
+
117
+ while current_is_value? && peek.include?(":")
118
+ key, value = shift.split(":", 2)
119
+ if hash.include? key
120
+ raise MalformattedArgumentError,
121
+ "You can't specify '#{key}' more than once in option " \
122
+ "'#{name}'; got #{key}:#{hash[key]} and #{key}:#{value}"
123
+ end
124
+ hash[key] = value
125
+ end
126
+ hash
127
+ end
128
+
129
+
130
+ # Runs through the argument array getting all strings until no string is
131
+ # found or a switch is found.
132
+ #
133
+ # ["a", "b", "c"]
134
+ #
135
+ # And returns it as an array:
136
+ #
137
+ # ["a", "b", "c"]
138
+ #
139
+ def parse_array(name)
140
+ return shift if peek.is_a?(Array)
141
+ array = []
142
+ array << shift while current_is_value?
143
+ array
144
+ end
145
+
146
+
147
+ # Check if the peek is numeric format and return a Float or Integer.
148
+ # Check if the peek is included in enum if enum is provided.
149
+ # Otherwise raises an error.
150
+ #
151
+ def parse_numeric(name)
152
+ return shift if peek.is_a?(Numeric)
153
+
154
+ unless peek =~ NUMERIC && $& == peek
155
+ raise MalformattedArgumentError,
156
+ "Expected numeric value for '#{name}'; got #{peek.inspect}"
157
+ end
144
158
 
145
- # Parse string:
146
- # for --string-arg, just return the current value in the pile
147
- # for --no-string-arg, nil
148
- # Check if the peek is included in enum if enum is provided. Otherwise raises an error.
149
- #
150
- def parse_string(name)
151
- if no_or_skip?(name)
152
- nil
153
- else
154
- value = shift
159
+ value = $&.index(".") ? shift.to_f : shift.to_i
155
160
  if @switches.is_a?(Hash) && switch = @switches[name]
156
161
  if switch.enum && !switch.enum.include?(value)
157
- raise MalformattedArgumentError, "Expected '#{name}' to be one of #{switch.enum.join(', ')}; got #{value}"
162
+ raise MalformattedArgumentError,
163
+ "Expected '#{name}' to be one of #{switch.enum.join(', ')}; " \
164
+ "got #{value}"
158
165
  end
159
166
  end
160
167
  value
161
168
  end
162
- end
163
-
164
- # Raises an error if @non_assigned_required array is not empty.
165
- #
166
- def check_requirement!
167
- return if @non_assigned_required.empty?
168
- names = @non_assigned_required.map do |o|
169
- o.respond_to?(:switch_name) ? o.switch_name : o.human_name
170
- end.join("', '")
171
- class_name = self.class.name.split("::").last.downcase
172
- raise RequiredArgumentMissingError, "No value provided for required #{class_name} '#{names}'"
173
- end
174
- end
175
- end
169
+
170
+
171
+ # Parse string:
172
+ # for --string-arg, just return the current value in the pile
173
+ # for --no-string-arg, nil
174
+ # Check if the peek is included in enum if enum is provided. Otherwise raises an error.
175
+ #
176
+ def parse_string(name)
177
+ if no_or_skip?(name)
178
+ nil
179
+ else
180
+ value = shift
181
+ if @switches.is_a?(Hash) && switch = @switches[name]
182
+ if switch.enum && !switch.enum.include?(value)
183
+ raise MalformattedArgumentError,
184
+ "Expected '#{name}' to be one of #{switch.enum.join(', ')}; " \
185
+ "got #{value}"
186
+ end
187
+ end
188
+ value
189
+ end
190
+ end
191
+
192
+
193
+ # Raises an error if @non_assigned_required array is not empty.
194
+ #
195
+ def check_requirement!
196
+ return if @non_assigned_required.empty?
197
+ names = @non_assigned_required.map do |o|
198
+ o.respond_to?(:switch_name) ? o.switch_name : o.human_name
199
+ end.join("', '")
200
+ class_name = self.class.name.split("::").last.downcase
201
+ raise RequiredArgumentMissingError,
202
+ "No value provided for required #{class_name} '#{names}'"
203
+ end
204
+
205
+ public # end private Instance Methods ************************************
206
+ end # class Arguments
207
+ end # class Thor