toys-core 0.6.1 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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