atli 0.1.9 → 0.1.10

Sign up to get free protection for your applications and to get access to all the features.
data/lib/thor/base.rb CHANGED
@@ -12,7 +12,6 @@ require 'nrser'
12
12
  # -----------------------------------------------------------------------
13
13
  require "thor/command"
14
14
  require "thor/core_ext/hash_with_indifferent_access"
15
- require "thor/core_ext/ordered_hash"
16
15
  require "thor/error"
17
16
  require "thor/invocation"
18
17
  require "thor/parser"
@@ -21,6 +20,9 @@ require "thor/line_editor"
21
20
  require "thor/util"
22
21
  require 'thor/execution'
23
22
  require 'thor/base/class_methods'
23
+ require 'thor/base/arguments_concern'
24
+ require 'thor/base/shared_options_concern'
25
+ require 'thor/base/shared_concern'
24
26
 
25
27
 
26
28
  # Refinements
@@ -48,8 +50,75 @@ class Thor
48
50
  # Shared base behavior included in {Thor} and {Thor::Group}.
49
51
  #
50
52
  module Base
53
+
54
+ # Module Methods
55
+ # ============================================================================
56
+
57
+ # Hook called when {Thor::Base} is mixed in ({Thor} and {Thor::Group}).
58
+ #
59
+ # Extends `base` with {Thor::Base::ClassMethods}, and includes
60
+ # {Thor::Invocation} and {Thor::Shell} in `base` as well.
61
+ #
62
+ # @param [Module] base
63
+ # Module (or Class) that included {Thor::Base}.
64
+ #
65
+ # @return [void]
66
+ #
67
+ def self.included base
68
+ base.extend ClassMethods
69
+ base.send :include, Invocation
70
+ base.send :include, Shell
71
+ base.send :include, ArgumentsConcern
72
+ base.send :include, SharedOptionsConcern
73
+ base.send :include, SharedConcern
74
+
75
+ base.no_commands {
76
+ base.send :include, NRSER::Log::Mixin
77
+ }
78
+
79
+ end
80
+
81
+ # Returns the classes that inherits from Thor or Thor::Group.
82
+ #
83
+ # ==== Returns
84
+ # Array[Class]
85
+ #
86
+ def self.subclasses
87
+ @subclasses ||= []
88
+ end
89
+
90
+ # Returns the files where the subclasses are kept.
91
+ #
92
+ # ==== Returns
93
+ # Hash[path<String> => Class]
94
+ #
95
+ def self.subclass_files
96
+ @subclass_files ||= Hash.new { |h, k| h[k] = [] }
97
+ end
98
+
99
+ # Whenever a class inherits from Thor or Thor::Group, we should track the
100
+ # class and the file on Thor::Base. This is the method responsible for it.
101
+ #
102
+ def self.register_klass_file(klass) #:nodoc:
103
+ file = caller[1].match(/(.*):\d+/)[1]
104
+ unless Thor::Base.subclasses.include?(klass)
105
+ Thor::Base.subclasses << klass
106
+ end
107
+
108
+ file_subclasses = Thor::Base.subclass_files[File.expand_path(file)]
109
+ file_subclasses << klass unless file_subclasses.include?(klass)
110
+ end
111
+
112
+
113
+ # Attributes
114
+ # ========================================================================
115
+
51
116
  attr_accessor :options, :parent_options, :args
52
117
 
118
+
119
+ # Construction
120
+ # ========================================================================
121
+
53
122
  # It receives arguments in an Array and two hashes, one for options and
54
123
  # other for configuration.
55
124
  #
@@ -67,50 +136,92 @@ class Thor
67
136
  #
68
137
  # config<Hash>:: Configuration for this Thor class.
69
138
  #
70
- def initialize(args = [], local_options = {}, config = {})
71
- logger.debug "#{ self.class.name }#initialize",
72
- args: args,
73
- local_options: local_options
74
-
75
- parse_options = self.class.class_options
76
-
77
- # The start method splits inbound arguments at the first argument
78
- # that looks like an option (starts with - or --). It then calls
79
- # new, passing in the two halves of the arguments Array as the
80
- # first two parameters.
81
-
82
- command_options = config.delete(:command_options) # hook for start
83
- parse_options = parse_options.merge(command_options) if command_options
84
- if local_options.is_a?(Array)
85
- array_options = local_options
86
- hash_options = {}
139
+ def initialize args = [], options_args_or_defaults = {}, config = {}
140
+ # Before {#initialize} is called arguments have been split out into
141
+ # either:
142
+ #
143
+ # 1. Positional `args` and args that need to be parsed for options.
144
+ #
145
+ # 2. Positional `args` and already parsed option values that we call
146
+ # "defaults".
147
+
148
+ if options_args_or_defaults.is_a? Array
149
+ # Case (1)
150
+ #
151
+ # The start method splits inbound arguments at the first argument
152
+ # that looks like an option (starts with - or --). It then calls
153
+ # new, passing in the two halves of the arguments Array as the
154
+ # first two parameters.
155
+ #
156
+ # In this case the first Array stays as `args` and the second Array
157
+ # gets assigned to `args_to_parse_for_options` to be parsed for
158
+ # options:
159
+ #
160
+ args_to_parse_for_options = options_args_or_defaults
161
+ option_defaults = {}
87
162
  else
163
+ # Case (2)
164
+ #
88
165
  # Handle the case where the class was explicitly instantiated
89
166
  # with pre-parsed options.
90
- array_options = []
91
- hash_options = local_options
167
+ #
168
+ args_to_parse_for_options = []
169
+ option_defaults = options_args_or_defaults
92
170
  end
93
171
 
94
- # Let Thor::Options parse the options first, so it can remove
172
+ logger.debug "#{ self.class.name }#initialize",
173
+ args: args,
174
+ options_args_or_defaults: options_args_or_defaults,
175
+ args_to_parse_for_options: args_to_parse_for_options,
176
+ option_defaults: option_defaults
177
+
178
+ # Let {Thor::Options} parse the options first, so it can remove
95
179
  # declared options from the array. This will leave us with
96
180
  # a list of arguments that weren't declared.
181
+
182
+ # Hash of {Thor::Option} that we can parse from the CLI args
183
+ # keyed by their {Thor::Option#name}.
184
+ #
185
+ # @type [HashWithIndifferentAccess<String, Thor::Option>]
186
+ #
187
+ options_to_parse_by_name = [
188
+ # Add class-level options
189
+ self.class.class_options,
190
+
191
+ # Options passed in by {Thor.dispatch} for the command to be
192
+ # invoked. May be `nil`, though I'm not totally sure when... in that
193
+ # case those options won't be parsed.
194
+ #
195
+ # This was
196
+ #
197
+ # config.delete( :command_options ),
198
+ #
199
+ # though I can't figure out why. Specs still pass without it..
200
+ #
201
+ config[:command_options],
202
+ ].compact.reduce( HashWithIndifferentAccess.new, :merge! )
203
+
97
204
  stop_on_unknown = \
98
205
  self.class.stop_on_unknown_option? config[:current_command]
206
+
99
207
  disable_required_check = \
100
208
  self.class.disable_required_check? config[:current_command]
209
+
210
+ logger.trace "Ready to create options",
211
+ options_to_parse_by_name: options_to_parse_by_name,
212
+ option_defaults: option_defaults,
213
+ stop_on_unknown: stop_on_unknown,
214
+ disable_required_check: disable_required_check,
215
+ args: args,
216
+ args_to_parse_for_options: args_to_parse_for_options
101
217
 
102
- logger.debug "Ready to create options",
103
- array_options: array_options,
104
- hash_options: hash_options,
105
- stop_on_unknown: stop_on_unknown,
106
- disable_required_check: disable_required_check
107
-
108
- opts = Thor::Options.new( parse_options,
109
- hash_options,
110
- stop_on_unknown,
111
- disable_required_check )
218
+ options_parser = Thor::Options.new \
219
+ options_to_parse_by_name,
220
+ option_defaults,
221
+ stop_on_unknown,
222
+ disable_required_check
112
223
 
113
- self.options = opts.parse(array_options)
224
+ self.options = options_parser.parse args_to_parse_for_options
114
225
 
115
226
  if config[:class_options]
116
227
  self.options = config[:class_options].merge(options)
@@ -118,7 +229,9 @@ class Thor
118
229
 
119
230
  # If unknown options are disallowed, make sure that none of the
120
231
  # remaining arguments looks like an option.
121
- opts.check_unknown! if self.class.check_unknown_options?(config)
232
+ if self.class.check_unknown_options? config
233
+ options_parser.check_unknown!
234
+ end
122
235
 
123
236
  # Add the remaining arguments from the options parser to the
124
237
  # arguments passed in to initialize. Then remove any positional
@@ -126,16 +239,26 @@ class Thor
126
239
  # by Thor::Group). Tis will leave us with the remaining
127
240
  # positional arguments.
128
241
  to_parse = args
129
- unless self.class.strict_args_position?(config)
130
- to_parse += opts.remaining
242
+ unless self.class.strict_args_position? config
243
+ to_parse += options_parser.remaining
131
244
  end
132
245
 
133
- thor_args = Thor::Arguments.new(self.class.arguments)
134
- thor_args.parse(to_parse).each { |k, v| __send__("#{k}=", v) }
246
+ thor_args = Thor::Arguments.new [
247
+ *self.class.class_arguments,
248
+ *config[:current_command]&.arguments,
249
+ ]
250
+
251
+ # Set the arguments as instance variables.
252
+ thor_args.parse( to_parse ).each { |k, v| __send__ "#{k}=", v }
253
+
254
+ # Set whatever is left as args
135
255
  @args = thor_args.remaining
136
256
  end
137
257
 
138
258
 
259
+ # Instance Methods
260
+ # ========================================================================
261
+
139
262
  protected
140
263
  # ========================================================================
141
264
 
@@ -202,60 +325,5 @@ class Thor
202
325
 
203
326
  # end protected
204
327
 
205
-
206
- # Module Methods
207
- # ============================================================================
208
-
209
- # Hook called when {Thor::Base} is mixed in ({Thor} and {Thor::Group}).
210
- #
211
- # Extends `base` with {Thor::Base::ClassMethods}, and includes
212
- # {Thor::Invocation} and {Thor::Shell} in `base` as well.
213
- #
214
- # @param [Module] base
215
- # Module (or Class) that included {Thor::Base}.
216
- #
217
- # @return [void]
218
- #
219
- def self.included base
220
- base.extend ClassMethods
221
- base.send :include, Invocation
222
- base.send :include, Shell
223
-
224
- base.no_commands {
225
- base.send :include, SemanticLogger::Loggable
226
- }
227
-
228
- end
229
-
230
- # Returns the classes that inherits from Thor or Thor::Group.
231
- #
232
- # ==== Returns
233
- # Array[Class]
234
- #
235
- def self.subclasses
236
- @subclasses ||= []
237
- end
238
-
239
- # Returns the files where the subclasses are kept.
240
- #
241
- # ==== Returns
242
- # Hash[path<String> => Class]
243
- #
244
- def self.subclass_files
245
- @subclass_files ||= Hash.new { |h, k| h[k] = [] }
246
- end
247
-
248
- # Whenever a class inherits from Thor or Thor::Group, we should track the
249
- # class and the file on Thor::Base. This is the method responsible for it.
250
- #
251
- def self.register_klass_file(klass) #:nodoc:
252
- file = caller[1].match(/(.*):\d+/)[1]
253
- unless Thor::Base.subclasses.include?(klass)
254
- Thor::Base.subclasses << klass
255
- end
256
-
257
- file_subclasses = Thor::Base.subclass_files[File.expand_path(file)]
258
- file_subclasses << klass unless file_subclasses.include?(klass)
259
- end
260
328
  end # module Base
261
329
  end # class Thor
@@ -0,0 +1,298 @@
1
+ # encoding: UTF-8
2
+ # frozen_string_literal: true
3
+ ##############################################################################
4
+ # Support for {Thor::Argument} in {Thor::Base} Classes
5
+ # ============================================================================
6
+ #
7
+ # With this file I've started to split the "Thor classes" stuff out by feature
8
+ # to make things easier to find and keep the files shorter.
9
+ #
10
+ ##############################################################################
11
+
12
+ # Requirements
13
+ # =======================================================================
14
+
15
+ # Deps
16
+ # -----------------------------------------------------------------------
17
+
18
+ require 'active_support/concern'
19
+
20
+
21
+ # Refinements
22
+ # =======================================================================
23
+
24
+ require 'nrser/refinements/types'
25
+ using NRSER::Types
26
+
27
+ # Namespace
28
+ # =======================================================================
29
+
30
+ class Thor
31
+ module Base
32
+
33
+ # Definitions
34
+ # =======================================================================
35
+
36
+
37
+ # @todo document Arguments module.
38
+ #
39
+ module ArgumentsConcern
40
+ extend ActiveSupport::Concern
41
+
42
+ # Instance Methods
43
+ # ========================================================================
44
+
45
+
46
+ class_methods do
47
+ # ==========================================================================
48
+
49
+ def arguments command: nil
50
+ [*class_arguments, *command&.arguments]
51
+ end
52
+
53
+
54
+ protected
55
+ # ========================================================================
56
+
57
+ def build_argument name, options, scope
58
+ name = name.to_s
59
+
60
+ is_thor_reserved_word? name, :argument
61
+ no_commands { attr_accessor name }
62
+
63
+ required = if options.key?(:optional)
64
+ !options[:optional]
65
+ elsif options.key?(:required)
66
+ options[:required]
67
+ else
68
+ # If neither `:required` or `:optional` options were provided,
69
+ # default to the argument being required if no `:default` was provided.
70
+ options[:default].nil?
71
+ end
72
+
73
+ scope.delete_if { |argument| argument.name == name }
74
+
75
+ if required
76
+ scope.each do |argument|
77
+ next if argument.required?
78
+ raise ArgumentError,
79
+ "You cannot have #{ name.inspect } as required argument " \
80
+ "after the non-required argument #{ argument.human_name.inspect }."
81
+ end
82
+ end
83
+
84
+ options[:required] = required
85
+
86
+ scope << Thor::Argument.new( name, options )
87
+ end
88
+
89
+
90
+ def remove_argument_from *names, scope:, undefine: false
91
+ names.each do |name|
92
+ scope.delete_if { |a| a.name == name.to_s }
93
+ undef_method name, "#{name}=" if undefine
94
+ end
95
+ end
96
+
97
+ public # end protected ***************************************************
98
+
99
+
100
+ # @!group Class-Level Argument Class Methods
101
+ # ------------------------------------------------------------------------
102
+
103
+ # Returns this class' class-level arguments, looking up in the ancestors
104
+ # chain.
105
+ #
106
+ # @return [Array<Thor::Argument>]
107
+ #
108
+ def class_arguments
109
+ @class_arguments ||= from_superclass( :class_arguments, [] )
110
+ end
111
+
112
+
113
+ # Adds an argument to the class and creates an attr_accessor for it.
114
+ #
115
+ # @note
116
+ # This used to just be called `.argument`, and apparently arguments
117
+ # were class-level **only**. I don't know why.
118
+ #
119
+ # Atli switches them to mirror options, with class and command levels.
120
+ #
121
+ # Arguments are different from options in several aspects. The first one
122
+ # is how they are parsed from the command line, arguments are retrieved
123
+ # from position:
124
+ #
125
+ # thor command NAME
126
+ #
127
+ # Instead of:
128
+ #
129
+ # thor command --name=NAME
130
+ #
131
+ # Besides, arguments are used inside your code as an accessor
132
+ # (self.argument), while options are all kept in a hash (self.options).
133
+ #
134
+ # Finally, arguments cannot have type :default or :boolean but can be
135
+ # optional (supplying :optional => :true or :required => false), although
136
+ # you cannot have a required argument after a non-required argument. If
137
+ # you try it, an error is raised.
138
+ #
139
+ # @param [String | Symbol] name
140
+ # The name of the argument.
141
+ #
142
+ # @param [Hash] options
143
+ #
144
+ # @option options [String] :desc
145
+ # Description for the argument.
146
+ #
147
+ # @option options [Boolean] :required
148
+ # If the argument is required or not (opposite of `:optional`).
149
+ #
150
+ # @option options [Boolean] :optional
151
+ # If the argument is optional or not (opposite of `:required`).
152
+ #
153
+ # @option options [:string | :hash | :array | :numeric] :type
154
+ # The type of the argument.
155
+ #
156
+ # @option options [Object] :default
157
+ # Default value for this argument. It cannot be required and
158
+ # have default values.
159
+ #
160
+ # @option options [String] :banner
161
+ # String to show on usage notes.
162
+ #
163
+ # @return (see #class_arguments)
164
+ #
165
+ # @raise [ArgumentError]
166
+ # Raised if you supply a required argument after a non-required one.
167
+ #
168
+ def class_argument name, **options
169
+ build_argument name, options, class_arguments
170
+ end
171
+
172
+
173
+ # Removes a previous defined class-level argument. If `:undefine` option is
174
+ # given, un-defines accessors as well.
175
+ #
176
+ # @param [Array<String | Symbol>] *names
177
+ # Arguments to be removed
178
+ #
179
+ # @param [Boolean] undefine:
180
+ # Un-defines the arguments' setter methods as well.
181
+ #
182
+ # @example
183
+ # remove_class_argument :foo
184
+ #
185
+ # @example
186
+ # remove_class_argument :foo, :bar, :baz, :undefine => true
187
+ #
188
+ def remove_class_argument *names, undefine: false
189
+ remove_argument_from *names, scope: class_arguments, undefine: undefine
190
+ end
191
+
192
+ # @!endgroup Class-Level Argument Class Methods # ************************
193
+
194
+
195
+ # @!group Method-Specific Argument Class Methods
196
+ # ------------------------------------------------------------------------
197
+
198
+ # Returns this class' class-level arguments, looking up in the ancestors
199
+ # chain.
200
+ #
201
+ # @return [Array<Thor::Argument>]
202
+ #
203
+ def method_arguments
204
+ @method_arguments ||= []
205
+ end
206
+
207
+
208
+ # Adds an argument to the class and creates an attr_accessor for it.
209
+ #
210
+ # @note
211
+ # This used to just be called `.argument`, and apparently arguments
212
+ # were class-level **only**. I don't know why.
213
+ #
214
+ # Atli switches them to mirror options, with class and command levels.
215
+ #
216
+ # Arguments are different from options in several aspects. The first one
217
+ # is how they are parsed from the command line, arguments are retrieved
218
+ # from position:
219
+ #
220
+ # thor command NAME
221
+ #
222
+ # Instead of:
223
+ #
224
+ # thor command --name=NAME
225
+ #
226
+ # Besides, arguments are used inside your code as an accessor
227
+ # (self.argument), while options are all kept in a hash (self.options).
228
+ #
229
+ # Finally, arguments cannot have type :default or :boolean but can be
230
+ # optional (supplying :optional => :true or :required => false), although
231
+ # you cannot have a required argument after a non-required argument. If
232
+ # you try it, an error is raised.
233
+ #
234
+ # @param [String | Symbol] name
235
+ # The name of the argument.
236
+ #
237
+ # @param [Hash] options
238
+ #
239
+ # @option options [String] :desc
240
+ # Description for the argument.
241
+ #
242
+ # @option options [Boolean] :required
243
+ # If the argument is required or not (opposite of `:optional`).
244
+ #
245
+ # @option options [Boolean] :optional
246
+ # If the argument is optional or not (opposite of `:required`).
247
+ #
248
+ # @option options [:string | :hash | :array | :numeric] :type
249
+ # The type of the argument.
250
+ #
251
+ # @option options [Object] :default
252
+ # Default value for this argument. It cannot be required and
253
+ # have default values.
254
+ #
255
+ # @option options [String] :banner
256
+ # String to show on usage notes.
257
+ #
258
+ # @return (see #class_arguments)
259
+ #
260
+ # @raise [ArgumentError]
261
+ # Raised if you supply a required argument after a non-required one.
262
+ #
263
+ def method_argument name, **options
264
+ build_argument( name, options, method_arguments )
265
+ end
266
+
267
+ alias_method :argument, :method_argument
268
+ alias_method :arg, :method_argument
269
+
270
+
271
+ # Removes a previous defined class-level argument. If `:undefine` option is
272
+ # given, un-defines accessors as well.
273
+ #
274
+ # @param [Array<String | Symbol>] *names
275
+ # Arguments to be removed
276
+ #
277
+ # @param [Boolean] undefine:
278
+ # Un-defines the arguments' setter methods as well.
279
+ #
280
+ # @example
281
+ #
282
+ # remove_argument :foo remove_argument :foo, :bar, :baz, :undefine => true
283
+ #
284
+ def remove_method_argument *names, undefine: false
285
+ remove_argument_from *names, scope: method_arguments, undefine: undefine
286
+ end
287
+
288
+ # @!endgroup Method-Specific Argument Class Methods # ********************
289
+
290
+ end # class_methods ********************************************************
291
+
292
+ end # module ArgumentsConcern
293
+
294
+ # /Namespace
295
+ # =======================================================================
296
+
297
+ end # module Base
298
+ end # class Thor