toys 0.3.0 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -34,6 +34,20 @@ module Toys
34
34
  # Namespace for common templates
35
35
  #
36
36
  module Templates
37
+ ##
38
+ # Return a template class by name.
39
+ #
40
+ # Currently recognized template names are:
41
+ #
42
+ # * `:clean` : Creates a tool that cleans build artifacts.
43
+ # * `:gem_build` : Creates a tool that builds and/or releases gems.
44
+ # * `:minitest` : Creates a tool that runs unit tests.
45
+ # * `:rubocop` : Creates a tool that runs rubocop.
46
+ # * `:yardoc` : Creates a tool that generates YARD documentation.
47
+ #
48
+ # @param [String,Symbol] name Name of the template class to return
49
+ # @return [Class,nil] The class, or `nil` if not found
50
+ #
37
51
  def self.lookup(name)
38
52
  Utils::ModuleLookup.lookup(:templates, name)
39
53
  end
@@ -35,6 +35,18 @@ module Toys
35
35
  class Clean
36
36
  include Template
37
37
 
38
+ ##
39
+ # Create the template settings for the Clean template.
40
+ #
41
+ # You may provide a hash of options when expanding this template.
42
+ # Supported options include:
43
+ #
44
+ # * `:name` (String) Name of the tool to create. Defaults to "clean".
45
+ # * `:paths` (Array<String>) An array of glob patterns indicating what
46
+ # to clean.
47
+ #
48
+ # @param [Hash] opts Options.
49
+ #
38
50
  def initialize(opts = {})
39
51
  @name = opts[:name] || "clean"
40
52
  @paths = opts[:paths] || []
@@ -44,7 +56,7 @@ module Toys
44
56
  attr_accessor :paths
45
57
 
46
58
  to_expand do |template|
47
- name(template.name) do
59
+ tool(template.name) do
48
60
  desc "Clean built files and directories."
49
61
 
50
62
  use :file_utils
@@ -52,7 +64,6 @@ module Toys
52
64
  execute do
53
65
  files = []
54
66
  patterns = Array(template.paths)
55
- patterns = ["lib/**/*.rb"] if patterns.empty?
56
67
  patterns.each do |pattern|
57
68
  files.concat(::Dir.glob(pattern))
58
69
  end
@@ -37,6 +37,24 @@ module Toys
37
37
  class GemBuild
38
38
  include Template
39
39
 
40
+ ##
41
+ # Create the template settings for the GemBuild template.
42
+ #
43
+ # You may provide a hash of options when expanding this template.
44
+ # Supported options include:
45
+ #
46
+ # * `:name` (String) Name of the tool to create. Defaults to "build".
47
+ # * `:gem_name` (String) Name of the gem to build. If not provided,
48
+ # defaults to the first gemspec file it finds.
49
+ # * `:push_gem` (Boolean) If true, pushes the built gem to rubygems.
50
+ # * `:tag` (Boolean) If true, tags the git repo with the gem version.
51
+ # * `:push_tag` (Boolean,String) If truthy, pushes the new tag to
52
+ # a git remote. You may specify which remote by setting the value to
53
+ # a string. Otherwise, if the value is simply `true`, the "origin"
54
+ # remote is used by default.
55
+ #
56
+ # @param [Hash] opts Options.
57
+ #
40
58
  def initialize(opts = {})
41
59
  @name = opts[:name] || "build"
42
60
  @gem_name = opts[:gem_name]
@@ -61,7 +79,7 @@ module Toys
61
79
  end
62
80
  task_type = template.push_gem ? "Release" : "Build"
63
81
 
64
- name(template.name) do
82
+ tool(template.name) do
65
83
  desc "#{task_type} the gem: #{template.gem_name}"
66
84
 
67
85
  use :file_utils
@@ -35,10 +35,25 @@ module Toys
35
35
  class Minitest
36
36
  include Template
37
37
 
38
+ ##
39
+ # Create the template settings for the Minitest template.
40
+ #
41
+ # You may provide a hash of options when expanding this template.
42
+ # Supported options include:
43
+ #
44
+ # * `:name` (String) Name of the tool to create. Defaults to "test".
45
+ # * `:lib` (Array<String>) An array of library paths to add to the
46
+ # ruby require path.
47
+ # * `:files` (Array<String>) An array of globs indicating the test
48
+ # files to load.
49
+ # * `:warnings` (Boolean) If true, runs tests with Ruby warnings.
50
+ #
51
+ # @param [Hash] opts Options.
52
+ #
38
53
  def initialize(opts = {})
39
54
  @name = opts[:name] || "test"
40
55
  @libs = opts[:libs] || ["lib"]
41
- @files = opts[:files] || ["test/test*.rb"]
56
+ @files = opts[:files] || ["test/**/test*.rb"]
42
57
  @warnings = opts.include?(:warnings) ? opts[:warnings] : true
43
58
  end
44
59
 
@@ -48,7 +63,7 @@ module Toys
48
63
  attr_accessor :warnings
49
64
 
50
65
  to_expand do |template|
51
- name(template.name) do
66
+ tool(template.name) do
52
67
  desc "Run minitest on the current project."
53
68
 
54
69
  use :exec
@@ -35,6 +35,19 @@ module Toys
35
35
  class Rubocop
36
36
  include Template
37
37
 
38
+ ##
39
+ # Create the template settings for the Rubocop template.
40
+ #
41
+ # You may provide a hash of options when expanding this template.
42
+ # Supported options include:
43
+ #
44
+ # * `:name` (String) Name of the tool to create. Defaults to "rubocop".
45
+ # * `:fail_on_error` (Boolean) If true, exits with a nonzero code if
46
+ # Rubocop fails. Defaults to true.
47
+ # * `:options` (Hash) Additional options passed to the Rubocop CLI.
48
+ #
49
+ # @param [Hash] opts Options.
50
+ #
38
51
  def initialize(opts = {})
39
52
  @name = opts[:name] || "rubocop"
40
53
  @fail_on_error = opts.include?(:fail_on_error) ? opts[:fail_on_error] : true
@@ -46,7 +59,7 @@ module Toys
46
59
  attr_accessor :options
47
60
 
48
61
  to_expand do |template|
49
- name(template.name) do
62
+ tool(template.name) do
50
63
  desc "Run rubocop on the current project."
51
64
 
52
65
  use :exec
@@ -35,6 +35,20 @@ module Toys
35
35
  class Yardoc
36
36
  include Template
37
37
 
38
+ ##
39
+ # Create the template settings for the Yardoc template.
40
+ #
41
+ # You may provide a hash of options when expanding this template.
42
+ # Supported options include:
43
+ #
44
+ # * `:name` (String) Name of the tool to create. Defaults to "yardoc".
45
+ # * `:files` (Array<String>) An array of globs indicating the files
46
+ # to document.
47
+ # * `:options` (Hash) Additional options passed to YARD
48
+ # * `:stats_options` (Hash) Additional options passed to YARD stats
49
+ #
50
+ # @param [Hash] opts Options.
51
+ #
38
52
  def initialize(opts = {})
39
53
  @name = opts[:name] || "yardoc"
40
54
  @files = opts[:files] || []
@@ -48,7 +62,7 @@ module Toys
48
62
  attr_accessor :stats_options
49
63
 
50
64
  to_expand do |template|
51
- name(template.name) do
65
+ tool(template.name) do
52
66
  desc "Run yardoc on the current project."
53
67
 
54
68
  use :exec
@@ -31,12 +31,21 @@ require "optparse"
31
31
 
32
32
  module Toys
33
33
  ##
34
- # A tool definition
34
+ # A Tool is a single command that can be invoked using Toys.
35
+ # It has a name, a series of one or more words that you use to identify
36
+ # the tool on the command line. It also has a set of formal switches and
37
+ # command line arguments supported, and a block that gets run when the
38
+ # tool is executed.
35
39
  #
36
40
  class Tool
37
- def initialize(full_name, middleware_stack)
38
- @full_name = full_name
39
- @middleware_stack = middleware_stack.dup
41
+ ##
42
+ # Create a new tool.
43
+ #
44
+ # @param [Array<String>] full_name The name of the tool
45
+ #
46
+ def initialize(full_name)
47
+ @full_name = full_name.dup.freeze
48
+ @middleware_stack = []
40
49
 
41
50
  @definition_path = nil
42
51
  @cur_path = nil
@@ -47,99 +56,200 @@ module Toys
47
56
  @long_desc = nil
48
57
 
49
58
  @default_data = {}
50
- @switches = []
51
- @required_args = []
52
- @optional_args = []
53
- @remaining_args = nil
59
+ @switch_definitions = []
60
+ @required_arg_definitions = []
61
+ @optional_arg_definitions = []
62
+ @remaining_args_definition = nil
54
63
 
55
64
  @helpers = {}
56
65
  @modules = []
57
66
  @executor = nil
58
67
  end
59
68
 
69
+ ##
70
+ # Return the name of the tool as an array of strings.
71
+ # This array may not be modified.
72
+ # @return [Array<String>]
73
+ #
60
74
  attr_reader :full_name
61
- attr_reader :switches
62
- attr_reader :required_args
63
- attr_reader :optional_args
64
- attr_reader :remaining_args
75
+
76
+ ##
77
+ # Return a list of all defined switches.
78
+ # @return [Array<Toys::Tool::SwitchDefinition>]
79
+ #
80
+ attr_reader :switch_definitions
81
+
82
+ ##
83
+ # Return a list of all defined required positional arguments.
84
+ # @return [Array<Toys::Tool::ArgDefinition>]
85
+ #
86
+ attr_reader :required_arg_definitions
87
+
88
+ ##
89
+ # Return a list of all defined optional positional arguments.
90
+ # @return [Array<Toys::Tool::ArgDefinition>]
91
+ #
92
+ attr_reader :optional_arg_definitions
93
+
94
+ ##
95
+ # Return the remaining arguments specification, or `nil` if remaining
96
+ # arguments are currently not supported by this tool.
97
+ # @return [Toys::Tool::ArgDefinition,nil]
98
+ #
99
+ attr_reader :remaining_args_definition
100
+
101
+ ##
102
+ # Return the default argument data.
103
+ # @return [Hash]
104
+ #
65
105
  attr_reader :default_data
106
+
107
+ ##
108
+ # Return a list of modules that will be available during execution.
109
+ # @return [Array<Module>]
110
+ #
66
111
  attr_reader :modules
112
+
113
+ ##
114
+ # Return a list of helper methods that will be available during execution.
115
+ # @return [Hash{Symbol => Proc}]
116
+ #
67
117
  attr_reader :helpers
118
+
119
+ ##
120
+ # Return the executor block, or `nil` if not present.
121
+ # @return [Proc,nil]
122
+ #
68
123
  attr_reader :executor
124
+
125
+ ##
126
+ # If this tool is an alias, return the alias target as a local name (i.e.
127
+ # a single word identifying a sibling of this tool). Returns `nil` if this
128
+ # tool is not an alias.
129
+ # @return [String,nil]
130
+ #
69
131
  attr_reader :alias_target
132
+
133
+ ##
134
+ # Returns the middleware stack
135
+ # @return [Array<Object>]
136
+ #
70
137
  attr_reader :middleware_stack
138
+
139
+ ##
140
+ # Returns the path to the file that contains the definition of this tool.
141
+ # @return [String]
142
+ #
71
143
  attr_reader :definition_path
72
144
 
145
+ ##
146
+ # Returns the local name of this tool.
147
+ # @return [String]
148
+ #
73
149
  def simple_name
74
150
  full_name.last
75
151
  end
76
152
 
153
+ ##
154
+ # Returns a displayable name of this tool, generally the full name
155
+ # delimited by spaces.
156
+ # @return [String]
157
+ #
77
158
  def display_name
78
159
  full_name.join(" ")
79
160
  end
80
161
 
162
+ ##
163
+ # Returns true if this tool is a root tool.
164
+ # @return [Boolean]
165
+ #
81
166
  def root?
82
167
  full_name.empty?
83
168
  end
84
169
 
170
+ ##
171
+ # Returns true if this tool has an executor defined.
172
+ # @return [Boolean]
173
+ #
85
174
  def includes_executor?
86
175
  executor.is_a?(::Proc)
87
176
  end
88
177
 
178
+ ##
179
+ # Returns true if this tool is an alias.
180
+ # @return [Boolean]
181
+ #
89
182
  def alias?
90
183
  !alias_target.nil?
91
184
  end
92
185
 
186
+ ##
187
+ # Returns the effective short description for this tool. This will be
188
+ # displayed when this tool is listed in a command list.
189
+ # @return [String]
190
+ #
93
191
  def effective_desc
94
192
  @desc || default_desc
95
193
  end
96
194
 
195
+ ##
196
+ # Returns the effective long description for this tool. This will be
197
+ # displayed as part of the usage for this particular tool.
198
+ # @return [String]
199
+ #
97
200
  def effective_long_desc
98
201
  @long_desc || @desc || default_desc
99
202
  end
100
203
 
204
+ ##
205
+ # Returns true if there is a specific description set for this tool.
206
+ # @return [Boolean]
207
+ #
101
208
  def includes_description?
102
209
  !@long_desc.nil? || !@desc.nil?
103
210
  end
104
211
 
212
+ ##
213
+ # Returns true if at least one switch or positional argument is defined
214
+ # for this tool.
215
+ # @return [Boolean]
216
+ #
105
217
  def includes_arguments?
106
- !default_data.empty? || !switches.empty? ||
107
- !required_args.empty? || !optional_args.empty? || !remaining_args.nil?
218
+ !default_data.empty? || !switch_definitions.empty? ||
219
+ !required_arg_definitions.empty? || !optional_arg_definitions.empty? ||
220
+ !remaining_args_definition.nil?
108
221
  end
109
222
 
223
+ ##
224
+ # Returns true if at least one helper method or module is added to this
225
+ # tool.
226
+ # @return [Boolean]
227
+ #
110
228
  def includes_helpers?
111
229
  !helpers.empty? || !modules.empty?
112
230
  end
113
231
 
232
+ ##
233
+ # Returns true if this tool has any definition information.
234
+ # @return [Boolean]
235
+ #
114
236
  def includes_definition?
115
237
  alias? || includes_arguments? || includes_executor? || includes_helpers?
116
238
  end
117
239
 
240
+ ##
241
+ # Returns a list of switch flags used by this tool.
242
+ # @return [Array<String>]
243
+ #
118
244
  def used_switches
119
- @switches.reduce([]) { |used, switch| used + switch.switches }.uniq
120
- end
121
-
122
- def defining_from(path)
123
- raise ToolDefinitionError, "Already being defined" if @cur_path
124
- @cur_path = path
125
- begin
126
- yield
127
- ensure
128
- @definition_path = @cur_path if includes_description? || includes_definition?
129
- @cur_path = nil
130
- end
131
- end
132
-
133
- def yield_definition
134
- saved_path = @cur_path
135
- @cur_path = nil
136
- begin
137
- yield
138
- ensure
139
- @cur_path = saved_path
140
- end
245
+ @switch_definitions.reduce([]) { |used, sdef| used + sdef.switches }.uniq
141
246
  end
142
247
 
248
+ ##
249
+ # Make this tool an alias of the sibling tool with the given local name.
250
+ #
251
+ # @param [String] target_word The name of the alias target
252
+ #
143
253
  def make_alias_of(target_word)
144
254
  if root?
145
255
  raise ToolDefinitionError, "Cannot make the root tool an alias"
@@ -152,16 +262,33 @@ module Toys
152
262
  self
153
263
  end
154
264
 
265
+ ##
266
+ # Set the short description.
267
+ #
268
+ # @param [String] str The short description
269
+ #
155
270
  def desc=(str)
156
271
  check_definition_state
157
272
  @desc = str
158
273
  end
159
274
 
275
+ ##
276
+ # Set the long description.
277
+ #
278
+ # @param [String] str The long description
279
+ #
160
280
  def long_desc=(str)
161
281
  check_definition_state
162
282
  @long_desc = str
163
283
  end
164
284
 
285
+ ##
286
+ # Define a helper method that will be available during execution.
287
+ # Pass the name of the method in the argument, and provide a block with
288
+ # the method body. Note the method name may not start with an underscore.
289
+ #
290
+ # @param [String] name The method name
291
+ #
165
292
  def add_helper(name, &block)
166
293
  check_definition_state
167
294
  name_str = name.to_s
@@ -172,6 +299,12 @@ module Toys
172
299
  self
173
300
  end
174
301
 
302
+ ##
303
+ # Mix in the given module during execution. You may provide the module
304
+ # itself, or the name of a well-known module under {Toys::Helpers}.
305
+ #
306
+ # @param [Module,String] name The module or module name.
307
+ #
175
308
  def use_module(name)
176
309
  check_definition_state
177
310
  case name
@@ -189,49 +322,174 @@ module Toys
189
322
  self
190
323
  end
191
324
 
325
+ ##
326
+ # Add a switch to the current tool. Each switch must specify a key which
327
+ # the executor may use to obtain the switch value from the context.
328
+ # You may then provide the switches themselves in `OptionParser` form.
329
+ #
330
+ # @param [Symbol] key The key to use to retrieve the value from the
331
+ # execution context.
332
+ # @param [String...] switches The switches in OptionParser format.
333
+ # @param [Object,nil] accept An OptionParser acceptor. Optional.
334
+ # @param [Object] default The default value. This is the value that will
335
+ # be set in the context if this switch is not provided on the command
336
+ # line. Defaults to `nil`.
337
+ # @param [String,nil] doc The documentation for the switch, which appears
338
+ # in the usage documentation. Defaults to `nil` for no documentation.
339
+ # @param [Boolean] only_unique If true, any switches that are already
340
+ # defined in this tool are removed from this switch. For example, if
341
+ # an earlier switch uses `-a`, and this switch wants to use both
342
+ # `-a` and `-b`, then only `-b` will be assigned to this switch.
343
+ # Defaults to false.
344
+ # @param [Proc,nil] handler An optional handler for setting/updating the
345
+ # value. If given, it should take two arguments, the new given value
346
+ # and the previous value, and it should return the new value that
347
+ # should be set. The default handler simply replaces the previous
348
+ # value. i.e. the default is effectively `-> (val, _prev) { val }`.
349
+ #
192
350
  def add_switch(key, *switches,
193
351
  accept: nil, default: nil, doc: nil, only_unique: false, handler: nil)
194
352
  check_definition_state
195
353
  switches << "--#{Tool.canonical_switch(key)}=VALUE" if switches.empty?
196
- switches << accept unless accept.nil?
197
- switches += Array(doc)
198
- switch_info = SwitchInfo.new(key, switches, handler)
354
+ bad_switch = switches.find { |s| Tool.extract_switch(s).empty? }
355
+ if bad_switch
356
+ raise ToolDefinitionError, "Illegal switch: #{bad_switch.inspect}"
357
+ end
358
+ switch_info = SwitchDefinition.new(key, switches + Array(accept) + Array(doc), handler)
199
359
  if only_unique
200
360
  switch_info.remove_switches(used_switches)
201
361
  end
202
362
  if switch_info.active?
203
363
  @default_data[key] = default
204
- @switches << switch_info
364
+ @switch_definitions << switch_info
205
365
  end
206
366
  self
207
367
  end
208
368
 
369
+ ##
370
+ # Add a required positional argument to the current tool. You must specify
371
+ # a key which the executor may use to obtain the argument value from the
372
+ # context.
373
+ #
374
+ # @param [Symbol] key The key to use to retrieve the value from the
375
+ # execution context.
376
+ # @param [Object,nil] accept An OptionParser acceptor. Optional.
377
+ # @param [String,nil] doc The documentation for the switch, which appears
378
+ # in the usage documentation. Defaults to `nil` for no documentation.
379
+ #
209
380
  def add_required_arg(key, accept: nil, doc: nil)
210
381
  check_definition_state
211
382
  @default_data[key] = nil
212
- @required_args << ArgInfo.new(key, accept, Array(doc))
383
+ @required_arg_definitions << ArgDefinition.new(key, accept, Array(doc))
213
384
  self
214
385
  end
215
386
 
387
+ ##
388
+ # Add an optional positional argument to the current tool. You must specify
389
+ # a key which the executor may use to obtain the argument value from the
390
+ # context. If an optional argument is not given on the command line, the
391
+ # value is set to the given default.
392
+ #
393
+ # @param [Symbol] key The key to use to retrieve the value from the
394
+ # execution context.
395
+ # @param [Object,nil] accept An OptionParser acceptor. Optional.
396
+ # @param [Object] default The default value. This is the value that will
397
+ # be set in the context if this argument is not provided on the command
398
+ # line. Defaults to `nil`.
399
+ # @param [String,nil] doc The documentation for the argument, which appears
400
+ # in the usage documentation. Defaults to `nil` for no documentation.
401
+ #
216
402
  def add_optional_arg(key, accept: nil, default: nil, doc: nil)
217
403
  check_definition_state
218
404
  @default_data[key] = default
219
- @optional_args << ArgInfo.new(key, accept, Array(doc))
405
+ @optional_arg_definitions << ArgDefinition.new(key, accept, Array(doc))
220
406
  self
221
407
  end
222
408
 
409
+ ##
410
+ # Specify what should be done with unmatched positional arguments. You must
411
+ # specify a key which the executor may use to obtain the remaining args
412
+ # from the context.
413
+ #
414
+ # @param [Symbol] key The key to use to retrieve the value from the
415
+ # execution context.
416
+ # @param [Object,nil] accept An OptionParser acceptor. Optional.
417
+ # @param [Object] default The default value. This is the value that will
418
+ # be set in the context if no unmatched arguments are provided on the
419
+ # command line. Defaults to the empty array `[]`.
420
+ # @param [String,nil] doc The documentation for the remaining arguments,
421
+ # which appears in the usage documentation. Defaults to `nil` for no
422
+ # documentation.
423
+ #
223
424
  def set_remaining_args(key, accept: nil, default: [], doc: nil)
224
425
  check_definition_state
225
426
  @default_data[key] = default
226
- @remaining_args = ArgInfo.new(key, accept, Array(doc))
427
+ @remaining_args_definition = ArgDefinition.new(key, accept, Array(doc))
227
428
  self
228
429
  end
229
430
 
431
+ ##
432
+ # Set the executor for this tool. This is a proc that will be called,
433
+ # with `self` set to a {Toys::Context}.
434
+ #
435
+ # @param [Proc] executor The executor for this tool.
436
+ #
230
437
  def executor=(executor)
231
438
  check_definition_state
232
439
  @executor = executor
233
440
  end
234
441
 
442
+ ##
443
+ # Execute this tool in the given context.
444
+ #
445
+ # @param [Toys::Context::Base] context_base The execution context
446
+ # @param [Array<String>] args The arguments to pass to the tool. Should
447
+ # not include the tool name.
448
+ # @param [Integer] verbosity The starting verbosity. Defaults to 0.
449
+ #
450
+ # @return [Integer] The result code.
451
+ #
452
+ def execute(context_base, args, verbosity: 0)
453
+ finish_definition unless @definition_finished
454
+ Execution.new(self).execute(context_base, args, verbosity: verbosity)
455
+ end
456
+
457
+ ##
458
+ # Declare that this tool is now defined in the given path
459
+ #
460
+ # @private
461
+ #
462
+ def defining_from(path)
463
+ raise ToolDefinitionError, "Already being defined" if @cur_path
464
+ @cur_path = path
465
+ begin
466
+ yield
467
+ ensure
468
+ @definition_path = @cur_path if includes_description? || includes_definition?
469
+ @cur_path = nil
470
+ end
471
+ end
472
+
473
+ ##
474
+ # Relinquish the current path declaration
475
+ #
476
+ # @private
477
+ #
478
+ def yield_definition
479
+ saved_path = @cur_path
480
+ @cur_path = nil
481
+ begin
482
+ yield
483
+ ensure
484
+ @cur_path = saved_path
485
+ end
486
+ end
487
+
488
+ ##
489
+ # Complete definition and run middleware configs
490
+ #
491
+ # @private
492
+ #
235
493
  def finish_definition
236
494
  if !alias? && !@definition_finished
237
495
  config_proc = proc {}
@@ -244,11 +502,6 @@ module Toys
244
502
  self
245
503
  end
246
504
 
247
- def execute(context_base, args, verbosity: 0)
248
- finish_definition unless @definition_finished
249
- Execution.new(self).execute(context_base, args, verbosity: verbosity)
250
- end
251
-
252
505
  private
253
506
 
254
507
  def make_config_proc(middleware, next_config)
@@ -282,69 +535,148 @@ module Toys
282
535
  end
283
536
 
284
537
  class << self
538
+ ## @private
285
539
  def canonical_switch(name)
286
540
  name.to_s.downcase.tr("_", "-").gsub(/[^a-z0-9-]/, "")
287
541
  end
542
+
543
+ ## @private
544
+ def extract_switch(str)
545
+ if !str.is_a?(String)
546
+ []
547
+ elsif str =~ /^(-[\?\w])(\s?\w+)?$/
548
+ [$1]
549
+ elsif str =~ /^--\[no-\](\w[\?\w-]*)$/
550
+ ["--#{$1}", "--no-#{$1}"]
551
+ elsif str =~ /^(--\w[\?\w-]*)([=\s]\w+)?$/
552
+ [$1]
553
+ else
554
+ []
555
+ end
556
+ end
288
557
  end
289
558
 
290
559
  ##
291
- # Representation of a formal switch
560
+ # Representation of a formal switch.
292
561
  #
293
- class SwitchInfo
562
+ class SwitchDefinition
563
+ ##
564
+ # Create a SwitchDefinition
565
+ #
566
+ # @param [Symbol] key This switch will set the given context key.
567
+ # @param [Array<String>] optparse_info The switch definition in
568
+ # OptionParser format
569
+ # @param [Proc,nil] handler An optional handler for setting/updating the
570
+ # value. If given, it should take two arguments, the new given value
571
+ # and the previous value, and it should return the new value that
572
+ # should be set. If `nil`, uses a default handler that just replaces
573
+ # the previous value. i.e. the default is effectively
574
+ # `-> (val, _prev) { val }`.
575
+ #
294
576
  def initialize(key, optparse_info, handler = nil)
295
577
  @key = key
296
578
  @optparse_info = optparse_info
297
- @handler = handler || ->(val, _cur) { val }
579
+ @handler = handler || ->(val, _prev) { val }
298
580
  @switches = nil
299
581
  end
300
582
 
583
+ ##
584
+ # Returns the key.
585
+ # @return [Symbol]
586
+ #
301
587
  attr_reader :key
588
+
589
+ ##
590
+ # Returns the OptionParser definition.
591
+ # @return [Array<String>]
592
+ #
302
593
  attr_reader :optparse_info
594
+
595
+ ##
596
+ # Returns the handler.
597
+ # @return [Proc]
598
+ #
303
599
  attr_reader :handler
304
600
 
601
+ ##
602
+ # Returns the list of switches used.
603
+ # @return [Array<String>]
604
+ #
305
605
  def switches
306
- @switches ||= optparse_info.map { |s| extract_switch(s) }.flatten
606
+ @switches ||= optparse_info.map { |s| Tool.extract_switch(s) }.flatten
307
607
  end
308
608
 
609
+ ##
610
+ # Returns true if this switch is active. That is, it has a nonempty
611
+ # switches list.
612
+ # @return [Boolean]
613
+ #
309
614
  def active?
310
615
  !switches.empty?
311
616
  end
312
617
 
618
+ ##
619
+ # Removes the given switches.
620
+ # @param [Array<String>] switches
621
+ #
313
622
  def remove_switches(switches)
314
623
  @optparse_info.select! do |s|
315
- extract_switch(s).all? { |ss| !switches.include?(ss) }
624
+ Tool.extract_switch(s).all? { |ss| !switches.include?(ss) }
316
625
  end
317
626
  @switches = nil
318
627
  self
319
628
  end
320
-
321
- def extract_switch(str)
322
- if str =~ /^(-[\?\w])/
323
- [$1]
324
- elsif str =~ /^--\[no-\](\w[\w-]*)/
325
- ["--#{$1}", "--no-#{$1}"]
326
- elsif str =~ /^(--\w[\w-]*)/
327
- [$1]
328
- else
329
- []
330
- end
331
- end
332
629
  end
333
630
 
334
631
  ##
335
- # Representation of a formal argument
632
+ # Representation of a formal positional argument
336
633
  #
337
- class ArgInfo
634
+ class ArgDefinition
635
+ ##
636
+ # Create an ArgDefinition
637
+ #
638
+ # @param [Symbol] key This argument will set the given context key.
639
+ # @param [Object] accept An OptionParser acceptor
640
+ # @param [Array<String>] doc An array of documentation strings
641
+ #
338
642
  def initialize(key, accept, doc)
339
643
  @key = key
340
644
  @accept = accept
341
645
  @doc = doc
342
646
  end
343
647
 
648
+ ##
649
+ # Returns the key.
650
+ # @return [Symbol]
651
+ #
344
652
  attr_reader :key
653
+
654
+ ##
655
+ # Returns the acceptor.
656
+ # @return [Object]
657
+ #
345
658
  attr_reader :accept
659
+
660
+ ##
661
+ # Returns the documentation strings.
662
+ # @return [Array<String>]
663
+ #
346
664
  attr_reader :doc
347
665
 
666
+ ##
667
+ # Return a canonical name for this arg. Used in usage documentation.
668
+ #
669
+ # @return [String]
670
+ #
671
+ def canonical_name
672
+ Tool.canonical_switch(key)
673
+ end
674
+
675
+ ##
676
+ # Process the given value through the acceptor.
677
+ #
678
+ # @private
679
+ #
348
680
  def process_value(val)
349
681
  return val unless accept
350
682
  n = canonical_name
@@ -354,10 +686,6 @@ module Toys
354
686
  optparse.parse(["--#{n}", val])
355
687
  result
356
688
  end
357
-
358
- def canonical_name
359
- Tool.canonical_switch(key)
360
- end
361
689
  end
362
690
 
363
691
  ##
@@ -409,7 +737,7 @@ module Toys
409
737
  optparse.remove
410
738
  optparse.new
411
739
  optparse.new
412
- @tool.switches.each do |switch|
740
+ @tool.switch_definitions.each do |switch|
413
741
  optparse.on(*switch.optparse_info) do |val|
414
742
  @data[switch.key] = switch.handler.call(val, @data[switch.key])
415
743
  end
@@ -418,7 +746,7 @@ module Toys
418
746
  end
419
747
 
420
748
  def parse_required_args(remaining, args)
421
- @tool.required_args.each do |arg_info|
749
+ @tool.required_arg_definitions.each do |arg_info|
422
750
  if remaining.empty?
423
751
  reason = "No value given for required argument named <#{arg_info.canonical_name}>"
424
752
  raise create_parse_error(args, reason)
@@ -429,7 +757,7 @@ module Toys
429
757
  end
430
758
 
431
759
  def parse_optional_args(remaining)
432
- @tool.optional_args.each do |arg_info|
760
+ @tool.optional_arg_definitions.each do |arg_info|
433
761
  break if remaining.empty?
434
762
  @data[arg_info.key] = arg_info.process_value(remaining.shift)
435
763
  end
@@ -438,15 +766,15 @@ module Toys
438
766
 
439
767
  def parse_remaining_args(remaining, args)
440
768
  return if remaining.empty?
441
- unless @tool.remaining_args
769
+ unless @tool.remaining_args_definition
442
770
  if @tool.includes_executor?
443
771
  raise create_parse_error(remaining, "Extra arguments provided")
444
772
  else
445
773
  raise create_parse_error(@tool.full_name + args, "Tool not found")
446
774
  end
447
775
  end
448
- @data[@tool.remaining_args.key] =
449
- remaining.map { |arg| @tool.remaining_args.process_value(arg) }
776
+ @data[@tool.remaining_args_definition.key] =
777
+ remaining.map { |arg| @tool.remaining_args_definition.process_value(arg) }
450
778
  end
451
779
 
452
780
  def create_parse_error(path, reason)