toys-core 0.6.1 → 0.7.0

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.
@@ -40,14 +40,17 @@ module Toys
40
40
  #
41
41
  class Flag
42
42
  ## @private
43
- def initialize(flags, accept, default, handler, report_collisions, desc, long_desc)
43
+ def initialize(flags, accept, default, handler, report_collisions,
44
+ group, desc, long_desc, display_name)
44
45
  @flags = flags
45
46
  @accept = accept
46
47
  @default = default
47
48
  @handler = handler
48
49
  @report_collisions = report_collisions
50
+ @group = group
49
51
  @desc = desc
50
52
  @long_desc = long_desc || []
53
+ @display_name = display_name
51
54
  end
52
55
 
53
56
  ##
@@ -55,7 +58,7 @@ module Toys
55
58
  # and the results are cumulative.
56
59
  #
57
60
  # @param [String...] flags
58
- # @return [Toys::DSL::Tool] self, for chaining.
61
+ # @return [Toys::DSL::Flag] self, for chaining.
59
62
  #
60
63
  def flags(*flags)
61
64
  @flags += flags
@@ -66,7 +69,7 @@ module Toys
66
69
  # Set the OptionParser acceptor.
67
70
  #
68
71
  # @param [Object] accept
69
- # @return [Toys::DSL::Tool] self, for chaining.
72
+ # @return [Toys::DSL::Flag] self, for chaining.
70
73
  #
71
74
  def accept(accept)
72
75
  @accept = accept
@@ -77,7 +80,7 @@ module Toys
77
80
  # Set the default value.
78
81
  #
79
82
  # @param [Object] default
80
- # @return [Toys::DSL::Tool] self, for chaining.
83
+ # @return [Toys::DSL::Flag] self, for chaining.
81
84
  #
82
85
  def default(default)
83
86
  @default = default
@@ -92,7 +95,7 @@ module Toys
92
95
  # responding to the `call` method) or you may pass a block.
93
96
  #
94
97
  # @param [Proc] handler
95
- # @return [Toys::DSL::Tool] self, for chaining.
98
+ # @return [Toys::DSL::Flag] self, for chaining.
96
99
  #
97
100
  def handler(handler = nil, &block)
98
101
  @handler = handler || block
@@ -104,7 +107,7 @@ module Toys
104
107
  # already in use or marked as disabled.
105
108
  #
106
109
  # @param [Boolean] setting
107
- # @return [Toys::DSL::Tool] self, for chaining.
110
+ # @return [Toys::DSL::Flag] self, for chaining.
108
111
  #
109
112
  def report_collisions(setting)
110
113
  @report_collisions = setting
@@ -116,7 +119,7 @@ module Toys
116
119
  # formats.
117
120
  #
118
121
  # @param [String,Array<String>,Toys::Utils::WrappableString] desc
119
- # @return [Toys::DSL::Tool] self, for chaining.
122
+ # @return [Toys::DSL::Flag] self, for chaining.
120
123
  #
121
124
  def desc(desc)
122
125
  @desc = desc
@@ -129,19 +132,42 @@ module Toys
129
132
  # allowed formats.
130
133
  #
131
134
  # @param [String,Array<String>,Toys::Utils::WrappableString...] long_desc
132
- # @return [Toys::DSL::Tool] self, for chaining.
135
+ # @return [Toys::DSL::Flag] self, for chaining.
133
136
  #
134
137
  def long_desc(*long_desc)
135
138
  @long_desc += long_desc
136
139
  self
137
140
  end
138
141
 
142
+ ##
143
+ # Set the group. A group may be set by name or group object. Setting
144
+ # `nil` selects the default group.
145
+ #
146
+ # @param [String,Symbol,Toys::Definition::FlagGroup,nil] group
147
+ # @return [Toys::DSL::Flag] self, for chaining.
148
+ #
149
+ def group(group)
150
+ @group = group
151
+ self
152
+ end
153
+
154
+ ##
155
+ # Set the display name. This may be used in help text and error messages.
156
+ #
157
+ # @param [String] display_name
158
+ # @return [Toys::DSL::Flag] self, for chaining.
159
+ #
160
+ def display_name(display_name)
161
+ @display_name = display_name
162
+ self
163
+ end
164
+
139
165
  ## @private
140
166
  def _add_to(tool, key)
141
167
  tool.add_flag(key, @flags,
142
168
  accept: @accept, default: @default, handler: @handler,
143
- report_collisions: @report_collisions,
144
- desc: @desc, long_desc: @long_desc)
169
+ report_collisions: @report_collisions, group: @group,
170
+ desc: @desc, long_desc: @long_desc, display_name: @display_name)
145
171
  end
146
172
  end
147
173
  end
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright 2018 Daniel Azuma
4
+ #
5
+ # All rights reserved.
6
+ #
7
+ # Redistribution and use in source and binary forms, with or without
8
+ # modification, are permitted provided that the following conditions are met:
9
+ #
10
+ # * Redistributions of source code must retain the above copyright notice,
11
+ # this list of conditions and the following disclaimer.
12
+ # * Redistributions in binary form must reproduce the above copyright notice,
13
+ # this list of conditions and the following disclaimer in the documentation
14
+ # and/or other materials provided with the distribution.
15
+ # * Neither the name of the copyright holder, nor the names of any other
16
+ # contributors to this software, may be used to endorse or promote products
17
+ # derived from this software without specific prior written permission.
18
+ #
19
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
23
+ # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29
+ # POSSIBILITY OF SUCH DAMAGE.
30
+ ;
31
+
32
+ module Toys
33
+ module DSL
34
+ ##
35
+ # DSL for a flag group definition block. Lets you create flags in a group.
36
+ #
37
+ # These directives are available inside a block passed to
38
+ # {Toys::DSL::Tool#flag_group} and related methods.
39
+ #
40
+ class FlagGroup
41
+ ## @private
42
+ def initialize(tool_dsl, tool_definition, flag_group)
43
+ @tool_dsl = tool_dsl
44
+ @tool_definition = tool_definition
45
+ @flag_group = flag_group
46
+ end
47
+
48
+ ##
49
+ # Add a flag to the current tool. Each flag must specify a key which
50
+ # the script may use to obtain the flag value from the context.
51
+ # You may then provide the flags themselves in OptionParser form.
52
+ #
53
+ # If the given key is a symbol representing a valid method name, then a
54
+ # helper method is automatically added to retrieve the value. Otherwise,
55
+ # if the key is a string or does not represent a valid method name, the
56
+ # tool can retrieve the value by calling {Toys::Tool#get}.
57
+ #
58
+ # Attributes of the flag may be passed in as arguments to this method, or
59
+ # set in a block passed to this method. If you provide a block, you can
60
+ # use directives in {Toys::DSL::Flag} within the block.
61
+ #
62
+ # @param [String,Symbol] key The key to use to retrieve the value from
63
+ # the execution context.
64
+ # @param [String...] flags The flags in OptionParser format.
65
+ # @param [Object] accept An acceptor that validates and/or converts the
66
+ # value. You may provide either the name of an acceptor you have
67
+ # defined, or one of the default acceptors provided by OptionParser.
68
+ # Optional. If not specified, accepts any value as a string.
69
+ # @param [Object] default The default value. This is the value that will
70
+ # be set in the context if this flag is not provided on the command
71
+ # line. Defaults to `nil`.
72
+ # @param [Proc,nil] handler An optional handler for setting/updating the
73
+ # value. If given, it should take two arguments, the new given value
74
+ # and the previous value, and it should return the new value that
75
+ # should be set. The default handler simply replaces the previous
76
+ # value. i.e. the default is effectively `-> (val, _prev) { val }`.
77
+ # @param [Boolean] report_collisions Raise an exception if a flag is
78
+ # requested that is already in use or marked as unusable. Default is
79
+ # true.
80
+ # @param [String,Array<String>,Toys::Utils::WrappableString] desc Short
81
+ # description for the flag. See {Toys::DSL::Tool#desc} for a
82
+ # description of the allowed formats. Defaults to the empty string.
83
+ # @param [Array<String,Array<String>,Toys::Utils::WrappableString>] long_desc
84
+ # Long description for the flag. See {Toys::DSL::Tool#long_desc} for
85
+ # a description of the allowed formats. (But note that this param
86
+ # takes an Array of description lines, rather than a series of
87
+ # arguments.) Defaults to the empty array.
88
+ # @param [String] display_name A display name for this flag, used in help
89
+ # text and error messages.
90
+ # @yieldparam flag_dsl [Toys::DSL::Flag] An object that lets you
91
+ # configure this flag in a block.
92
+ # @return [Toys::DSL::Tool] self, for chaining.
93
+ #
94
+ def flag(key, *flags,
95
+ accept: nil, default: nil, handler: nil,
96
+ report_collisions: true,
97
+ desc: nil, long_desc: nil, display_name: nil,
98
+ &block)
99
+ flag_dsl = DSL::Flag.new(flags, accept, default, handler, report_collisions,
100
+ @flag_group, desc, long_desc, display_name)
101
+ flag_dsl.instance_exec(flag_dsl, &block) if block
102
+ flag_dsl._add_to(@tool_definition, key)
103
+ DSL::Tool.maybe_add_getter(@tool_dsl, key)
104
+ self
105
+ end
106
+ end
107
+ end
108
+ end
@@ -324,6 +324,174 @@ module Toys
324
324
  self
325
325
  end
326
326
 
327
+ ##
328
+ # Create a flag group. If a block is given, flags defined in the block
329
+ # belong to the group. The flags in the group are listed together in
330
+ # help screens.
331
+ #
332
+ # Example:
333
+ #
334
+ # flag_group desc: "Debug Flags" do
335
+ # flag :debug, "-D", desc: "Enable debugger"
336
+ # flag :warnings, "-W[VAL]", desc: "Enable warnings"
337
+ # end
338
+ #
339
+ # @param [Symbol] type The type of group. Allowed values: `:required`,
340
+ # `:optional`, `:exactly_one`, `:at_most_one`, `:at_least_one`.
341
+ # Default is `:optional`.
342
+ # @param [String,Array<String>,Toys::Utils::WrappableString] desc Short
343
+ # description for the group. See {Toys::Definition::Tool#desc=} for a
344
+ # description of allowed formats. Defaults to `"Flags"`.
345
+ # @param [Array<String,Array<String>,Toys::Utils::WrappableString>] long_desc
346
+ # Long description for the flag group. See
347
+ # {Toys::Definition::Tool#long_desc=} for a description of allowed
348
+ # formats. Defaults to the empty array.
349
+ # @param [String,Symbol,nil] name The name of the group, or nil for no
350
+ # name.
351
+ # @param [Boolean] report_collisions If `true`, raise an exception if a
352
+ # the given name is already taken. If `false`, ignore. Default is
353
+ # `true`.
354
+ # @param [Boolean] prepend If `true`, prepend rather than append the
355
+ # group to the list. Default is `false`.
356
+ # @yieldparam flag_group_dsl [Toys::DSL::FlagGroup] An object that lets
357
+ # add flags to this group in a block.
358
+ # @return [Toys::DSL::Tool] self, for chaining.
359
+ #
360
+ def flag_group(type: :optional, desc: nil, long_desc: nil, name: nil,
361
+ report_collisions: true, prepend: false, &block)
362
+ cur_tool = DSL::Tool.current_tool(self, true)
363
+ return self if cur_tool.nil?
364
+ cur_tool.add_flag_group(type: type, desc: desc, long_desc: long_desc, name: name,
365
+ report_collisions: report_collisions, prepend: prepend)
366
+ group = prepend ? cur_tool.flag_groups.first : cur_tool.flag_groups.last
367
+ flag_group_dsl = DSL::FlagGroup.new(self, cur_tool, group)
368
+ flag_group_dsl.instance_exec(flag_group_dsl, &block) if block
369
+ self
370
+ end
371
+
372
+ ##
373
+ # Create a flag group of type `:required`. If a block is given, flags
374
+ # defined in the block belong to the group. All flags in this group are
375
+ # required.
376
+ #
377
+ # Example:
378
+ #
379
+ # all_required do
380
+ # flag :username, "--username=VAL", desc: "Set the username (required)"
381
+ # flag :password, "--password=VAL", desc: "Set the password (required)"
382
+ # end
383
+ #
384
+ # @param [String,Array<String>,Toys::Utils::WrappableString] desc Short
385
+ # description for the group. See {Toys::Definition::Tool#desc=} for a
386
+ # description of allowed formats. Defaults to `"Flags"`.
387
+ # @param [Array<String,Array<String>,Toys::Utils::WrappableString>] long_desc
388
+ # Long description for the flag group. See
389
+ # {Toys::Definition::Tool#long_desc=} for a description of allowed
390
+ # formats. Defaults to the empty array.
391
+ # @param [String,Symbol,nil] name The name of the group, or nil for no
392
+ # name.
393
+ # @param [Boolean] report_collisions If `true`, raise an exception if a
394
+ # the given name is already taken. If `false`, ignore. Default is
395
+ # `true`.
396
+ # @param [Boolean] prepend If `true`, prepend rather than append the
397
+ # group to the list. Default is `false`.
398
+ # @yieldparam flag_group_dsl [Toys::DSL::FlagGroup] An object that lets
399
+ # add flags to this group in a block.
400
+ # @return [Toys::DSL::Tool] self, for chaining.
401
+ #
402
+ def all_required(desc: nil, long_desc: nil, name: nil, report_collisions: true,
403
+ prepend: false, &block)
404
+ flag_group(type: :required, desc: desc, long_desc: long_desc,
405
+ name: name, report_collisions: report_collisions, prepend: prepend, &block)
406
+ end
407
+
408
+ ##
409
+ # Create a flag group of type `:at_most_one`. If a block is given, flags
410
+ # defined in the block belong to the group. At most one flag in this
411
+ # group must be provided on the command line.
412
+ #
413
+ # @param [String,Array<String>,Toys::Utils::WrappableString] desc Short
414
+ # description for the group. See {Toys::Definition::Tool#desc=} for a
415
+ # description of allowed formats. Defaults to `"Flags"`.
416
+ # @param [Array<String,Array<String>,Toys::Utils::WrappableString>] long_desc
417
+ # Long description for the flag group. See
418
+ # {Toys::Definition::Tool#long_desc=} for a description of allowed
419
+ # formats. Defaults to the empty array.
420
+ # @param [String,Symbol,nil] name The name of the group, or nil for no
421
+ # name.
422
+ # @param [Boolean] report_collisions If `true`, raise an exception if a
423
+ # the given name is already taken. If `false`, ignore. Default is
424
+ # `true`.
425
+ # @param [Boolean] prepend If `true`, prepend rather than append the
426
+ # group to the list. Default is `false`.
427
+ # @yieldparam flag_group_dsl [Toys::DSL::FlagGroup] An object that lets
428
+ # add flags to this group in a block.
429
+ # @return [Toys::DSL::Tool] self, for chaining.
430
+ #
431
+ def at_most_one_required(desc: nil, long_desc: nil, name: nil, report_collisions: true,
432
+ prepend: false, &block)
433
+ flag_group(type: :at_most_one, desc: desc, long_desc: long_desc,
434
+ name: name, report_collisions: report_collisions, prepend: prepend, &block)
435
+ end
436
+
437
+ ##
438
+ # Create a flag group of type `:at_least_one`. If a block is given, flags
439
+ # defined in the block belong to the group. At least one flag in this
440
+ # group must be provided on the command line.
441
+ #
442
+ # @param [String,Array<String>,Toys::Utils::WrappableString] desc Short
443
+ # description for the group. See {Toys::Definition::Tool#desc=} for a
444
+ # description of allowed formats. Defaults to `"Flags"`.
445
+ # @param [Array<String,Array<String>,Toys::Utils::WrappableString>] long_desc
446
+ # Long description for the flag group. See
447
+ # {Toys::Definition::Tool#long_desc=} for a description of allowed
448
+ # formats. Defaults to the empty array.
449
+ # @param [String,Symbol,nil] name The name of the group, or nil for no
450
+ # name.
451
+ # @param [Boolean] report_collisions If `true`, raise an exception if a
452
+ # the given name is already taken. If `false`, ignore. Default is
453
+ # `true`.
454
+ # @param [Boolean] prepend If `true`, prepend rather than append the
455
+ # group to the list. Default is `false`.
456
+ # @yieldparam flag_group_dsl [Toys::DSL::FlagGroup] An object that lets
457
+ # add flags to this group in a block.
458
+ # @return [Toys::DSL::Tool] self, for chaining.
459
+ #
460
+ def at_least_one_required(desc: nil, long_desc: nil, name: nil, report_collisions: true,
461
+ prepend: false, &block)
462
+ flag_group(type: :at_least_one, desc: desc, long_desc: long_desc,
463
+ name: name, report_collisions: report_collisions, prepend: prepend, &block)
464
+ end
465
+
466
+ ##
467
+ # Create a flag group of type `:exactly_one`. If a block is given, flags
468
+ # defined in the block belong to the group. Exactly one flag in this
469
+ # group must be provided on the command line.
470
+ #
471
+ # @param [String,Array<String>,Toys::Utils::WrappableString] desc Short
472
+ # description for the group. See {Toys::Definition::Tool#desc=} for a
473
+ # description of allowed formats. Defaults to `"Flags"`.
474
+ # @param [Array<String,Array<String>,Toys::Utils::WrappableString>] long_desc
475
+ # Long description for the flag group. See
476
+ # {Toys::Definition::Tool#long_desc=} for a description of allowed
477
+ # formats. Defaults to the empty array.
478
+ # @param [String,Symbol,nil] name The name of the group, or nil for no
479
+ # name.
480
+ # @param [Boolean] report_collisions If `true`, raise an exception if a
481
+ # the given name is already taken. If `false`, ignore. Default is
482
+ # `true`.
483
+ # @param [Boolean] prepend If `true`, prepend rather than append the
484
+ # group to the list. Default is `false`.
485
+ # @yieldparam flag_group_dsl [Toys::DSL::FlagGroup] An object that lets
486
+ # add flags to this group in a block.
487
+ # @return [Toys::DSL::Tool] self, for chaining.
488
+ #
489
+ def exactly_one_required(desc: nil, long_desc: nil, name: nil, report_collisions: true,
490
+ prepend: false, &block)
491
+ flag_group(type: :exactly_one, desc: desc, long_desc: long_desc,
492
+ name: name, report_collisions: report_collisions, prepend: prepend, &block)
493
+ end
494
+
327
495
  ##
328
496
  # Add a flag to the current tool. Each flag must specify a key which
329
497
  # the script may use to obtain the flag value from the context.
@@ -356,6 +524,9 @@ module Toys
356
524
  # @param [Boolean] report_collisions Raise an exception if a flag is
357
525
  # requested that is already in use or marked as unusable. Default is
358
526
  # true.
527
+ # @param [Toys::Definition::FlagGroup,String,Symbol,nil] group Group for
528
+ # this flag. You may provide a group name, a FlagGroup object, or
529
+ # `nil` which denotes the default group.
359
530
  # @param [String,Array<String>,Toys::Utils::WrappableString] desc Short
360
531
  # description for the flag. See {Toys::DSL::Tool#desc} for a
361
532
  # description of the allowed formats. Defaults to the empty string.
@@ -364,19 +535,21 @@ module Toys
364
535
  # a description of the allowed formats. (But note that this param
365
536
  # takes an Array of description lines, rather than a series of
366
537
  # arguments.) Defaults to the empty array.
538
+ # @param [String] display_name A display name for this flag, used in help
539
+ # text and error messages.
367
540
  # @yieldparam flag_dsl [Toys::DSL::Flag] An object that lets you
368
541
  # configure this flag in a block.
369
542
  # @return [Toys::DSL::Tool] self, for chaining.
370
543
  #
371
544
  def flag(key, *flags,
372
545
  accept: nil, default: nil, handler: nil,
373
- report_collisions: true,
374
- desc: nil, long_desc: nil,
546
+ report_collisions: true, group: nil,
547
+ desc: nil, long_desc: nil, display_name: nil,
375
548
  &block)
376
549
  cur_tool = DSL::Tool.current_tool(self, true)
377
550
  return self if cur_tool.nil?
378
- flag_dsl = DSL::Flag.new(flags, accept, default, handler,
379
- report_collisions, desc, long_desc)
551
+ flag_dsl = DSL::Flag.new(flags, accept, default, handler, report_collisions,
552
+ group, desc, long_desc, display_name)
380
553
  flag_dsl.instance_exec(flag_dsl, &block) if block
381
554
  flag_dsl._add_to(cur_tool, key)
382
555
  DSL::Tool.maybe_add_getter(self, key)
@@ -88,8 +88,9 @@ module Toys
88
88
  end
89
89
 
90
90
  def parse_args(args, data)
91
- optparse = create_option_parser(data)
91
+ optparse, seen = create_option_parser(data)
92
92
  remaining = optparse.parse(args)
93
+ validate_flags(args, seen)
93
94
  remaining = parse_required_args(remaining, args, data)
94
95
  remaining = parse_optional_args(remaining, data)
95
96
  parse_remaining_args(remaining, args, data)
@@ -98,6 +99,7 @@ module Toys
98
99
  end
99
100
 
100
101
  def create_option_parser(data)
102
+ seen = []
101
103
  optparse = ::OptionParser.new
102
104
  # The following clears out the Officious (hidden default flags).
103
105
  optparse.remove
@@ -106,13 +108,21 @@ module Toys
106
108
  optparse.new
107
109
  @tool_definition.flag_definitions.each do |flag|
108
110
  optparse.on(*flag.optparser_info) do |val|
111
+ seen << flag.key
109
112
  data[flag.key] = flag.handler.call(val, data[flag.key])
110
113
  end
111
114
  end
112
115
  @tool_definition.custom_acceptors do |accept|
113
116
  optparse.accept(accept)
114
117
  end
115
- optparse
118
+ [optparse, seen]
119
+ end
120
+
121
+ def validate_flags(args, seen)
122
+ @tool_definition.flag_groups.each do |group|
123
+ error = group.validation_error(seen)
124
+ raise create_parse_error(args, error) if error
125
+ end
116
126
  end
117
127
 
118
128
  def parse_required_args(remaining, args, data)
@@ -148,7 +158,7 @@ module Toys
148
158
  end
149
159
 
150
160
  def create_parse_error(path, reason)
151
- OptionParser::ParseError.new(*path).tap do |e|
161
+ ::OptionParser::ParseError.new(*path).tap do |e|
152
162
  e.reason = reason
153
163
  end
154
164
  end