toys 0.3.0 → 0.3.1

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