atli 0.1.4 → 0.1.5

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.
@@ -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