arg-parser 0.3.1 → 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9b0c2c32a741e3751760248db85d2cc4e1a47288
4
- data.tar.gz: cbd2b833b43077480b73e932bdf45168c52d8d04
3
+ metadata.gz: 0ff4e92f478416dc226d866f7763340e5bad9be7
4
+ data.tar.gz: d367fbaa43753a938f599ea0cf04181a3cabaadd
5
5
  SHA512:
6
- metadata.gz: 571fb0dda1c9f9b9162fe126da1795c22db68e3ae2732b6b66db0fbe87466cd617f8fb7a413f44b30ad21e47aa2d22b563030e4133151509766a49ad4506bd33
7
- data.tar.gz: 1dd67d98bedea2a677490e999fc9db92ee5c29ab1099740f4a0f828a33938ae4640220436140b247f72fdcc09c9361833df921a20b23f36fe389a515ff05877d
6
+ metadata.gz: 8c1994d8da4359cc9c19735975840835b156cd8d79afd255660e9285bcfec1bb18eb806230113da2103d129ac5abf725c38af4fd60ac052eeaa9ba4512ec4a85
7
+ data.tar.gz: 7dd2c412243ea6b4f15d0ea41fccec25ff173404817536e72aec01112e43ddc99ae52a16fd3748fcb908601bc2fbe75e4f3b875cd1c953b97d47869a5d1b0e3f
data/README.md CHANGED
@@ -47,6 +47,12 @@ class MyClass
47
47
  # opts.flag, and opts.files
48
48
  # ...
49
49
  else
50
+ # False is returned if argument parsing was not completed
51
+ # This may be due to an error or because the help command
52
+ # was used (by specifying --help or /?). The #show_help?
53
+ # method returns true if help was requested, otherwise if
54
+ # a parse error was encountered, #show_usage? is true and
55
+ # parse errors are in #parse_errors
50
56
  show_help? ? show_help : show_usage
51
57
  end
52
58
  end
@@ -63,6 +69,27 @@ MyClass.new.run
63
69
  ArgParser provides a fairly broad range of functionality for argument parsing. Following is a non-exhaustive
64
70
  list of features:
65
71
  * Built-in usage and help display
72
+ * Multiple different types of arguments can be defined:
73
+ - Command arguments indicate an argument that can be one of a limited selection of pre-defined
74
+ argument values. Each command argument must define its commands, as well as any command-specific
75
+ arguments.
76
+ - Positional arguments are arguments that are normally supplied without any key, and are matched
77
+ to an argument definition based on the order in which they are encountered. Positional arguments
78
+ can also be supplied using the key if preferred. Generally only a small number of positional
79
+ arguments would be defined for those parameters required on every run.
80
+ - Keyword arguments are arguments that must be specified by using a key to indicate which argument
81
+ the subsequent value is for.
82
+ - Flag arguments are boolean arguments that normally default to nil, but can be set to true just
83
+ by specifying the argument key. It is also possible to define flag arguments with a default value
84
+ of true; the argument can then be set to false by speciying the argument name with --no-<flag>
85
+ (or --not-<flag> or --non-<flag> when those are more understandable).
86
+ - Rest arguments use a single argument key to collect all remaining values from the command-line,
87
+ and return these as an array in the resulting argument results.
88
+ * Parsed results are returned in an OpenStruct, with every defined argument having a value set (whether
89
+ or not it was encountered on the command-line). Use the `default: <value>` option to set default
90
+ values for non-specified arguments where you don't want nil returned in the parse results. Note that
91
+ in the case of command arguments with command-specific arguments, only the arguments from the selected
92
+ command appear in the results.
66
93
  * Mandatory vs Optional: All arguments your program accepts can be defined as optional or mandatory.
67
94
  By default, positional arguments are considered mandatory, and all others default to optional. To change
68
95
  the default, simply specify `required: true` or `required: false` when defining the argument.
@@ -72,6 +99,10 @@ list of features:
72
99
  parsed value in the results. However, arguments can also define a single letter or digit short-key form
73
100
  which can be used as an alternate means for indicating a value. To define a short key, simply pass
74
101
  `short_key: '<letter_or_digit>'` when defining an argument.
102
+ * Requiring from a set of arguments: If you need to ensure that only one of several mutually exclusive
103
+ arguments is specified, use `require_one_of <arg1>, <arg2>, ..., <argN>`. If you need to ensure that
104
+ at least one argument is specified from a list of arguments, use
105
+ `require_any_of <arg1>, <arg2>, ..., <argN>`
75
106
  * Validation: Arguments can define validation requirements that must be satisfied. This can take several
76
107
  forms:
77
108
  - List of values: Pass an array containing the allowed values the argument can take.
@@ -90,3 +121,149 @@ list of features:
90
121
  * Pre-defined arguments: Arguments can be registered under a key, and then re-used across multiple
91
122
  definitions via the #predefined_arg DSL method. Common arguments would typically be defined in a
92
123
  shared file that was included into each job. See Argument.register and DSL.predefined_arg.
124
+
125
+
126
+ ### Commands
127
+
128
+ Often command-line utilities can perform several actions, and we want to define an argument that will
129
+ be used to indicate to the program which action it should perform. This can be done very easily using
130
+ positional arguments, and setting a list of allowed values in the argument validation, e.g.
131
+ `positional_arg :command, 'The command to perform', validation: %w{list export import}`
132
+
133
+ However, it is often the case that the different commands require different additional arguments
134
+ to be specified, and this makes it necessary to make any argument not required by all commands into
135
+ an optional argument. However, now the program has to ensure that it checks for those optional
136
+ arguments that are required for the command to actually be present.
137
+
138
+ In this circumstance, it is better to instead make use of command arguments. These are arguments
139
+ that look like positional arguments that accept only a specific set of values, however, each command
140
+ can then define its own set of additional arguments (of any type). During parsing, when a command
141
+ argument is found, the argument definition is updated on-the-fly to include any additional arguments
142
+ defined by the now-matched command. Additionally, the usage and help displays are also updated to
143
+ reflect the arguments defined by the specified command.
144
+
145
+ Defining command arguments is a little more complex, due to the need to associate command arguments
146
+ with the commands.
147
+
148
+
149
+ ```ruby
150
+ require 'arg-parser'
151
+
152
+ class MyClass
153
+
154
+ include ArgParser::DSL
155
+
156
+ purpose <<-EOT
157
+ This is where you specify the purpose of your program. It will be displayed in the
158
+ generated help screen if you pass /? or --help on the command-line.
159
+ EOT
160
+
161
+ command_arg :command, 'The command to perform' do
162
+ # We often want the same arguments to appear in several but not all commands.
163
+ # Rather than re-defining them for each command, we can first define them, and
164
+ # then reference them in the commands that need them.
165
+ define_args do
166
+ positional_arg :source, 'The source to use'
167
+ positional_arg :target, 'The target to use'
168
+ positional_arg :local_dir, 'The path to a local directory',
169
+ default: 'extracts'
170
+ end
171
+
172
+ command :list, 'List available items for transfer' do
173
+ predefined_arg :source
174
+ end
175
+ command :export, 'Export items to a local directory' do
176
+ predefined_arg :source
177
+ predefined_arg :local_dir
178
+ end
179
+ command :import, 'Import items from a local directory' do
180
+ predefined_arg :local_dir
181
+ predefined_arg :target
182
+ end
183
+ end
184
+
185
+ positional_arg :user_id, 'The user id for connecting to the source/target'
186
+ positional_arg :password, 'The password for connecting to the source/target',
187
+ sensitive: true
188
+
189
+ ...
190
+
191
+ def run
192
+ if opts = parse_arguments
193
+ case opts.command
194
+ when 'list'
195
+ do_list(opts.source)
196
+ when 'export'
197
+ do_export(opts.source, opts.local_dir)
198
+ when 'import'
199
+ do_import(opts.local_dir, opts.target)
200
+ end
201
+ else
202
+ show_help? ? show_help : show_usage
203
+ end
204
+ end
205
+
206
+ end
207
+
208
+ MyClass.new.run
209
+ ```
210
+
211
+ ## Pre-defined Arguments
212
+
213
+ We saw above in the case of commands that it can be useful to pre-define an argument with all its
214
+ parameters once, and then reference that argument in multiple separate commands. This allows us
215
+ to follow DRY principles when defining an argument, making maintenance simpler and reducing the
216
+ possibility of mis-configuring one or more instances of the same argument.
217
+
218
+ This principle does not only apply to arguments for use within a single utility or set of commands.
219
+ Arguments can also be pre-defined in an argument scope, and then referenced in multiple utilities.
220
+ Thus if there are common arguments needed in many utilities that share some common code, you can
221
+ also define these common arguments in the shared code, and then include the pre-defined arguments
222
+ into each utility that shares that functionality.
223
+
224
+
225
+ ```ruby
226
+ require 'arg-parser'
227
+
228
+ class MyLibrary
229
+
230
+ include ArgParser::DSL
231
+
232
+ LIBRARY_ARGS = define_args('Database Args') do
233
+ positional_arg :database, 'The name of the database to connect to',
234
+ on_parse: lambda{ |val, arg, hsh| # Lookup database connection details and return as hash }
235
+ positional_arg :user_id, 'The user id for connecting to the database'
236
+ positional_arg :password, 'The password for connecting to the database',
237
+ sensitive: true
238
+ end
239
+
240
+ end
241
+
242
+
243
+ class Utility
244
+
245
+ include 'arg-parser'
246
+
247
+ with_predefined_args MyLibrary::LIBRARY_ARGS do
248
+ predefined_arg :database
249
+ predefined_arg :user_id
250
+ predefined_arg :password
251
+ end
252
+
253
+ keyword_arg :log_level, 'Set the log level for database interactions'
254
+
255
+ ...
256
+
257
+
258
+ def run
259
+ if opts = parse_arguments
260
+ # Connect to database, set log level etc
261
+ else
262
+ show_help? ? show_help : show_usage
263
+ end
264
+ end
265
+
266
+ end
267
+
268
+ MyClass.new.run
269
+ ```
@@ -5,7 +5,11 @@ module ArgParser
5
5
  OnParseHandlers = {
6
6
  :split_to_array => lambda{ |val, arg, hsh| val.split(',') }
7
7
  }
8
- # Hash containing registered arguments available via #predefined_arg
8
+ # Hash containing globally registered predefined arguments available via
9
+ # #predefined_arg.
10
+ # Predefined arguments should normally be defined within an ArgumentScope,
11
+ # rather than globally. See DSL.define_args for pre-defining arguments on
12
+ # an including class, or use the argument creation methods on ArgumentScope,
9
13
  PredefinedArguments = { }
10
14
 
11
15
 
@@ -14,14 +18,12 @@ module ArgParser
14
18
  # @abstract
15
19
  class Argument
16
20
 
17
- # The key used to identify this argument value in the parsed command-
18
- # line results Struct.
19
21
  # @return [Symbol] the key/method by which this argument can be retrieved
20
22
  # from the parse result Struct.
21
23
  attr_reader :key
22
24
  # @return [String] the description for this argument, which will be shown
23
25
  # in the usage display.
24
- attr_reader :description
26
+ attr_accessor :description
25
27
  # @return [Symbol] a single letter or digit that can be used as a short
26
28
  # alternative to the full key to identify an argument value in a command-
27
29
  # line.
@@ -50,20 +52,19 @@ module ArgParser
50
52
  # the group.
51
53
  attr_accessor :usage_break
52
54
 
53
-
54
55
  # Converts an argument key specification into a valid key, by stripping
55
56
  # leading dashes, converting remaining dashes to underscores, and lower-
56
57
  # casing all text. This is required to ensure the key name will be a
57
58
  # valid accessor name on the parse results.
58
59
  #
60
+ # @param label [String|Symbol] the value supplied for an argument key
59
61
  # @return [Symbol] the key by which an argument can be retrieved from
60
- # the arguments definition, and the parse results.
62
+ # the arguments definition and the parse results.
61
63
  def self.to_key(label)
62
64
  k = label.to_s.gsub(/^-+/, '').gsub('-', '_')
63
65
  k.length > 1 ? k.downcase.intern : k.intern
64
66
  end
65
67
 
66
-
67
68
  # Register a common argument for use in multiple argument definitions. The
68
69
  # registered argument is a completely defined argument that can be added to
69
70
  # any argument definition via Definition#predefined_arg.
@@ -86,7 +87,6 @@ module ArgParser
86
87
  PredefinedArguments[key] = arg
87
88
  end
88
89
 
89
-
90
90
  # Return a copy of a pre-defined argument for use in an argument
91
91
  # definition.
92
92
  #
@@ -104,9 +104,22 @@ module ArgParser
104
104
  arg.clone
105
105
  end
106
106
 
107
+ # Set the short key (a single letter or digit) that may be used as an
108
+ # alternative when specifyin gthis argument.
109
+ #
110
+ # @param sk [String] The short key specification
111
+ def short_key=(sk)
112
+ if sk =~ /^-?([a-z0-9])$/i
113
+ @short_key = $1.intern
114
+ else
115
+ raise ArgumentError, "An argument short key must be a single digit or letter"
116
+ end
117
+ end
118
+
107
119
 
108
120
  private
109
121
 
122
+ # Private to prevent instantiation of this abstract class
110
123
  def initialize(key, desc, opts = {}, &block)
111
124
  @key = self.class.to_key(key)
112
125
  @description = desc
@@ -125,12 +138,100 @@ module ArgParser
125
138
  end
126
139
  @usage_break = opts[:usage_break]
127
140
  if sk = opts[:short_key]
128
- if sk =~ /^-?([a-z0-9])$/i
129
- @short_key = $1.intern
141
+ self.short_key=(sk)
142
+ end
143
+ end
144
+
145
+ end
146
+
147
+
148
+ # An argument type that defines a command. Each command placeholder can have
149
+ # one or more command arguments (i.e. allowed values). Depending on the command,
150
+ # different additional arguments may then be specified in the command's
151
+ # ArgumentSet.
152
+ class CommandArgument < Argument
153
+
154
+ # @return [Array<Symbol] List of valid commands that may be specified.
155
+ attr_accessor :commands
156
+
157
+
158
+ # Create an instance of a CommandArgument, a positional argument that
159
+ # indicates a particular command to be invoked.
160
+ #
161
+ # @param key [String|Symbol] The value under which the specified command
162
+ # can be retrieved following parsing (e.g. :command)
163
+ # @param desc [String] A description for the command argument.
164
+ # @param opts [Hash] An options hash. See Argument#initialize for supported
165
+ # option values.
166
+ def initialize(key, desc, opts = {})
167
+ super(key, desc, opts)
168
+ @commands = {}
169
+ @required = opts.fetch(:required, !opts.has_key?(:default))
170
+ @usage_value = opts.fetch(:usage_value, key.to_s.gsub('_', '-').upcase)
171
+ end
172
+
173
+ # Adds a specific command verb/value to this command argument
174
+ def <<(cmd_instance)
175
+ raise ArgumentError, "must be a CommandInstance object" unless cmd_instance.is_a?(CommandInstance)
176
+ @commands[cmd_instance.command_value] = cmd_instance
177
+ end
178
+
179
+ # Return the CommandInstance for a particular command value
180
+ #
181
+ # @param cmd_val [String|Symbol] A command token identifying the command
182
+ # @return [CommandInstance] The CommandInstance for the specified command
183
+ def [](cmd_val)
184
+ k = Argument.to_key(cmd_val)
185
+ @commands[k]
186
+ end
187
+
188
+ def to_s
189
+ @usage_value
190
+ end
191
+
192
+ def to_use
193
+ required? ? usage_value : "[#{usage_value}]"
194
+ end
195
+
196
+ end
197
+
198
+
199
+ # Represents a specific command value for a CommandArgument, along with any
200
+ # additional arguments specific to this command.
201
+ class CommandInstance < Argument
202
+
203
+ # @return the constant value that identifies this CommandInstance
204
+ attr_reader :command_value
205
+ # Return the CommandArgument to which this CommandInstance relates
206
+ attr_reader :command_arg
207
+ # Return an ArgumentScope for any additional arguments this command takes
208
+ attr_reader :argument_scope
209
+
210
+
211
+ def initialize(cmd_val, desc, cmd_arg, arg_scope, opts = {})
212
+ if cmd_arg.on_parse
213
+ if cmd_inst_op = opts[:on_parse]
214
+ # Command and command instance both defined on_parse handlers
215
+ opts[:on_parse] = lambda do |val, arg, hsh|
216
+ cmd_arg.on_parse(val, arg, hsh)
217
+ cmd_inst_op.on_parse(val, arg, hsh)
218
+ end
130
219
  else
131
- raise ArgumentError, "An argument short key must be a single digit or letter"
220
+ opts[:on_parse] = cmd_arg.on_parse
132
221
  end
133
222
  end
223
+ super(cmd_arg.key, desc, opts)
224
+ @command_value = cmd_val
225
+ @command_arg = cmd_arg
226
+ @argument_scope = arg_scope
227
+ end
228
+
229
+ def to_s
230
+ @command_value
231
+ end
232
+
233
+ def to_use
234
+ @command_value
134
235
  end
135
236
 
136
237
  end
@@ -320,6 +421,7 @@ module ArgParser
320
421
  # String to some other type.
321
422
  def initialize(key, desc, opts = {}, &block)
322
423
  super
424
+ @usage_value = opts[:usage_value]
323
425
  end
324
426
 
325
427
  def required
@@ -332,7 +434,11 @@ module ArgParser
332
434
 
333
435
  def to_use
334
436
  sk = short_key ? "-#{short_key}, " : ''
335
- "#{sk}#{self.to_s}"
437
+ if @usage_value
438
+ "#{sk}#{@usage_value[0..1] == '--' ? '' : '--'}#{@usage_value}"
439
+ else
440
+ "#{sk}#{self.to_s}"
441
+ end
336
442
  end
337
443
 
338
444
  end
@@ -366,6 +472,7 @@ module ArgParser
366
472
  def initialize(key, desc, opts = {}, &block)
367
473
  super
368
474
  @min_values = opts.fetch(:min_values, opts.fetch(:required, true) ? 1 : 0)
475
+ @default = [@default] if @default.is_a?(String)
369
476
  end
370
477
 
371
478
  def required
@@ -5,25 +5,76 @@ module ArgParser
5
5
  class NoSuchArgumentError < RuntimeError; end
6
6
 
7
7
 
8
- # Represents the collection of possible command-line arguments for a script.
9
- class Definition
8
+ # Represents a scope within which an argument is defined/alid.
9
+ # Scopes may be nested, and argument requests will search the scope chain to
10
+ # find a matching argument.
11
+ class ArgumentScope
10
12
 
11
- # @return [String] A title for the script, displayed at the top of the
12
- # usage and help outputs.
13
- attr_accessor :title
14
- # @return [String] A short description of the purpose of the script, for
15
- # display when showing the usage help.
16
- attr_accessor :purpose
13
+ attr_reader :name, :parent
14
+ attr_accessor :predefined_args
17
15
 
18
16
 
19
- # Create a new Definition, which is a collection of valid Arguments to
20
- # be used when parsing a command-line.
21
- def initialize
17
+ def initialize(name, parent = nil)
18
+ @name = name
19
+ @parent = parent
20
+ @parent.add_child(self) if @parent
21
+ @children = []
22
22
  @arguments = {}
23
23
  @short_keys = {}
24
- @require_set = Hash.new{ |h,k| h[k] = [] }
25
- @title = $0.respond_to?(:titleize) ? $0.titleize : $0
26
- yield self if block_given?
24
+ @predefined_args = nil
25
+ end
26
+
27
+
28
+ # Adds a Scope as a child of this scope.
29
+ def add_child(arg_scope)
30
+ raise ArgumentError, "#{arg_scope} must be an ArgumentScope instance" unless arg_scope.is_a?(ArgumentScope)
31
+ raise ArgumentError, "#{arg_scope} parent not set to this ArgumentScope" if arg_scope.parent != self
32
+ @children << arg_scope
33
+ end
34
+
35
+
36
+ # Checks if a key has been used in this scope, any ancestor scopes, or
37
+ # any descendant scopes.
38
+ #
39
+ # @param key [String] The key under which an argument is to be registered
40
+ # @return [Argument|nil] The Argument that already uses the key, or nil
41
+ # if the key is not used.
42
+ def key_used?(key)
43
+ self.walk_ancestors do |anc|
44
+ arg = anc.has_key?(key)
45
+ return arg if arg
46
+ end
47
+ self.walk_children do |child|
48
+ arg = child.has_key?(key)
49
+ return arg if arg
50
+ end
51
+ nil
52
+ end
53
+
54
+
55
+ # Yields each key/argument pair for this ArgumentScope
56
+ def walk_arguments(&blk)
57
+ @arguments.each(&blk)
58
+ end
59
+
60
+
61
+ # Yields each ancestor of this scope, optionally including this scope.
62
+ def walk_ancestors(inc_self = true, &blk)
63
+ scope = inc_self ? self : self.parent
64
+ while scope do
65
+ yield scope
66
+ scope = scope.parent
67
+ end
68
+ end
69
+
70
+
71
+ # Recursively walks and yields each descendant scopes of this scope.
72
+ def walk_children(inc_self = false, &blk)
73
+ yield self if inc_self
74
+ @children.each do |child|
75
+ yield child
76
+ child.walk_children(&blk)
77
+ end
27
78
  end
28
79
 
29
80
 
@@ -50,26 +101,35 @@ module ArgParser
50
101
  # line definition.
51
102
  def <<(arg)
52
103
  case arg
53
- when PositionalArgument, KeywordArgument, FlagArgument, RestArgument
54
- if @arguments[arg.key]
55
- raise ArgumentError, "An argument with key '#{arg.key}' has already been defined"
104
+ when CommandArgument, PositionalArgument, KeywordArgument, FlagArgument, RestArgument
105
+ if used = self.key_used?(arg.key)
106
+ raise ArgumentError, "An argument with key '#{arg.key}' has already been defined: #{used}"
56
107
  end
57
- if arg.short_key && @short_keys[arg.short_key]
58
- raise ArgumentError, "The short key '#{arg.short_key}' has already been registered by the '#{
59
- @short_keys[arg.short_key]}' argument"
108
+ if arg.short_key && used = self.key_used?(arg.short_key)
109
+ raise ArgumentError, "The short key '#{arg.short_key}' has already been registered: #{used}"
60
110
  end
61
- if arg.is_a?(RestArgument) && rest_args
111
+ if arg.is_a?(RestArgument) && rest_args?
62
112
  raise ArgumentError, "Only one rest argument can be defined"
63
113
  end
64
114
  @arguments[arg.key] = arg
65
115
  @short_keys[arg.short_key] = arg if arg.short_key
66
116
  else
67
- raise ArgumentError, "arg must be an instance of PositionalArgument, KeywordArgument, " +
68
- "FlagArgument or RestArgument"
117
+ raise ArgumentError, "arg must be an instance of CommandArgument, PositionalArgument, " +
118
+ "KeywordArgument, FlagArgument or RestArgument"
69
119
  end
70
120
  end
71
121
 
72
122
 
123
+ # Add a command argument to the set of arguments in this command-line
124
+ # argument definition.
125
+ # @see CommandArgument#initialize
126
+ def command_arg(key, desc, opts = {}, &block)
127
+ cmd_arg = ArgParser::CommandArgument.new(key, desc, opts)
128
+ CommandBlock.new(self, cmd_arg, &block)
129
+ self << cmd_arg
130
+ end
131
+
132
+
73
133
  # Add a positional argument to the set of arguments in this command-line
74
134
  # argument definition.
75
135
  # @see PositionalArgument#initialize
@@ -123,108 +183,23 @@ module ArgParser
123
183
  # returned in the command-line parse results if no other value is
124
184
  # specified.
125
185
  def predefined_arg(lookup_key, opts = {})
126
- arg = Argument.lookup(lookup_key)
127
- arg.description = opts[:description] if opts[:description]
128
- arg.useage_break = opts[:usage_break] if opts.has_key?(:usage_break)
186
+ #arg = Argument.lookup(lookup_key)
187
+ arg = nil
188
+ self.walk_ancestors do |scope|
189
+ arg = scope.predefined_args && scope.predefined_args.has_key?(lookup_key)
190
+ break if arg
191
+ end
192
+ raise ArgumentError, "No predefined argument with key '#{lookup_key}' found" unless arg
193
+ arg.short_key = opts[:short_key] if opts.has_key?(:short_key)
194
+ arg.description = opts[:description] if opts.has_key?(:description)
195
+ arg.usage_break = opts[:usage_break] if opts.has_key?(:usage_break)
129
196
  arg.required = opts[:required] if opts.has_key?(:required)
130
197
  arg.default = opts[:default] if opts.has_key?(:default)
198
+ arg.on_parse = opts[:on_parse] if opts.has_key?(:on_parse)
131
199
  self << arg
132
200
  end
133
201
 
134
202
 
135
- # Individual arguments are optional, but exactly one of +keys+ arguments
136
- # is required.
137
- def require_one_of(*keys)
138
- @require_set[:one] << keys.map{ |k| self[k] }
139
- end
140
-
141
-
142
- # Individual arguments are optional, but at least one of +keys+ arguments
143
- # is required.
144
- def require_any_of(*keys)
145
- @require_set[:any] << keys.map{ |k| self[k] }
146
- end
147
-
148
-
149
- # True if at least one argument is required out of multiple optional args.
150
- def requires_some?
151
- @require_set.size > 0
152
- end
153
-
154
-
155
- # @return [Parser] a Parser instance that can be used to parse this
156
- # command-line Definition.
157
- def parser
158
- @parser ||= Parser.new(self)
159
- end
160
-
161
-
162
- # Parse the +args+ array of arguments using this command-line definition.
163
- #
164
- # @param args [Array, String] an array of arguments, or a String representing
165
- # the command-line that is to be parsed.
166
- # @return [OpenStruct, false] if successful, an OpenStruct object with all
167
- # arguments defined as accessors, and the parsed or default values for each
168
- # argument as values. If unsuccessful, returns false indicating a parse
169
- # failure.
170
- # @see Parser#parse, Parser#errors, Parser#show_usage, Parser#show_help
171
- def parse(args = ARGV)
172
- parser.parse(args)
173
- end
174
-
175
-
176
- # Return an array of parse errors.
177
- # @see Parser#errors
178
- def errors
179
- parser.errors
180
- end
181
-
182
-
183
- # Whether user indicated they would like help on usage.
184
- # @see Parser#show_usage
185
- def show_usage?
186
- parser.show_usage?
187
- end
188
-
189
-
190
- # Whether user indicated they would like help on supported arguments.
191
- # @see Parser#show_help
192
- def show_help?
193
- parser.show_help?
194
- end
195
-
196
-
197
- # Validates the supplied +args+ Hash object, verifying that any argument
198
- # set requirements have been satisfied. Returns an array of error
199
- # messages for each set requirement that is not satisfied.
200
- #
201
- # @param args [Hash] a Hash containing the keys and values identified
202
- # by the parser.
203
- # @return [Array] a list of errors for any argument requirements that
204
- # have not been satisfied.
205
- def validate_requirements(args)
206
- errors = []
207
- @require_set.each do |req, sets|
208
- sets.each do |set|
209
- count = set.count{ |arg| args.has_key?(arg.key) && args[arg.key] }
210
- case req
211
- when :one
212
- if count == 0
213
- errors << "No argument has been specified for one of: #{set.join(', ')}"
214
- elsif count > 1
215
- errors << "Only one argument can been specified from: #{set.join(', ')}"
216
- end
217
- when :any
218
- if count == 0
219
- errors << "At least one of the arguments must be specified from: #{set.join(', ')}"
220
- end
221
- end
222
- end
223
- end
224
- errors
225
- end
226
-
227
-
228
203
  # @return [Array] all argument keys that have been defined.
229
204
  def keys
230
205
  @arguments.keys
@@ -243,9 +218,23 @@ module ArgParser
243
218
  end
244
219
 
245
220
 
221
+ # @return [Array] all command arguments that have been defined
222
+ def command_args
223
+ @arguments.values.select{ |arg| CommandArgument === arg }
224
+ end
225
+
226
+
227
+ # @return True if a command arg has been defined
228
+ def command_args?
229
+ command_args.size > 0
230
+ end
231
+
232
+
246
233
  # @return [Array] all positional arguments that have been defined
247
234
  def positional_args
248
- @arguments.values.select{ |arg| PositionalArgument === arg }
235
+ @arguments.values.select{ |arg| CommandArgument === arg ||
236
+ CommandInstance === arg ||
237
+ PositionalArgument === arg }
249
238
  end
250
239
 
251
240
 
@@ -258,7 +247,10 @@ module ArgParser
258
247
  # @return [Array] the non-positional (i.e. keyword and flag)
259
248
  # arguments that have been defined.
260
249
  def non_positional_args
261
- @arguments.values.reject{ |arg| PositionalArgument === arg || RestArgument === arg }
250
+ @arguments.values.reject{ |arg| CommandArgument === arg ||
251
+ CommandInstance === arg ||
252
+ PositionalArgument === arg ||
253
+ RestArgument === arg }
262
254
  end
263
255
 
264
256
 
@@ -317,13 +309,232 @@ module ArgParser
317
309
  @arguments.size
318
310
  end
319
311
 
312
+ end
313
+
314
+
315
+ # Used to define arguments specific to a CommandInstance
316
+ class CommandBlock
317
+
318
+ def initialize(arg_scope, cmd_arg, &block)
319
+ @parent = arg_scope
320
+ @command_arg = cmd_arg
321
+ self.instance_eval(&block)
322
+ end
323
+
324
+
325
+ def define_args(&block)
326
+ pre_def_arg_scope = ArgumentScope.new("Predefined args for #{@command_arg}")
327
+ pre_def_arg_scope.instance_eval(&block)
328
+ @parent.predefined_args = pre_def_arg_scope
329
+ end
330
+
331
+
332
+ def command(key, desc, opts = {}, &block)
333
+ cmd_arg_scope = ArgumentScope.new("Arguments for #{key} command", @parent)
334
+ cmd_arg_scope.instance_eval(&block) if block_given?
335
+ cmd_inst = CommandInstance.new(key, desc, @command_arg, cmd_arg_scope, opts)
336
+ @command_arg << cmd_inst
337
+ end
338
+
339
+ end
340
+
341
+
342
+ # Represents the collection of possible command-line arguments for a script.
343
+ class Definition < ArgumentScope
344
+
345
+ # @return [String] A title for the script, displayed at the top of the
346
+ # usage and help outputs.
347
+ attr_accessor :title
348
+ # @return [String] A short description of the purpose of the script, for
349
+ # display when showing the usage help.
350
+ attr_accessor :purpose
351
+ # @return [String] A copyright notice, displayed in the usage and help
352
+ # outputs.
353
+ attr_accessor :copyright
354
+
355
+
356
+ # Create a new Definition, which is a collection of valid Arguments to
357
+ # be used when parsing a command-line.
358
+ def initialize(name = 'ArgParser::Definition')
359
+ super(name)
360
+ @require_set = []
361
+ @title = $0.respond_to?(:titleize) ? $0.titleize : $0
362
+ yield self if block_given?
363
+ end
364
+
365
+
366
+ # Collapses an ArgumentScope into this Definition, representing the
367
+ # collapsed argument possibilities once a command has been identitfied
368
+ # for a CommandArgument. Think of the original Definition as being a
369
+ # superposition of possible argument definitions, with one possible
370
+ # state for each CommandInstance of each commad. Once the actual
371
+ # CommandInstance is known, we are collapsing the superposition of
372
+ # possible definitions to a lower dimensionality; only one possible
373
+ # definition remains once all CommandArgument objects are replaced by
374
+ # CommandInstances.
375
+ #
376
+ # @param cmd_inst [CommandInstance] The instance of a command that has
377
+ # been specified.
378
+ # @return [Definition] A new Definition with a set of arguments combined
379
+ # from this Definition and the selected ArgumentScope for a specific
380
+ # command instance.
381
+ def collapse(cmd_inst)
382
+ new_def = self.clone
383
+ child = cmd_inst.argument_scope
384
+ new_args = {}
385
+ new_short_keys = {}
386
+ @arguments.each do |key, arg|
387
+ if arg == cmd_inst.command_arg
388
+ new_args[key] = cmd_inst
389
+ child.walk_arguments do |key, arg|
390
+ new_args[key] = arg
391
+ new_short_keys[arg.short_key] = arg if arg.short_key
392
+ end
393
+ else
394
+ new_args[key] = arg
395
+ new_short_keys[arg.short_key] = arg if arg.short_key
396
+ end
397
+ end
398
+ new_children = @children.reject{ |c| c == cmd_inst.argument_scope } &
399
+ child.instance_variable_get(:@children)
400
+ new_def.instance_variable_set(:@arguments, new_args)
401
+ new_def.instance_variable_set(:@short_keys, new_short_keys)
402
+ new_def.instance_variable_set(:@children, new_children)
403
+ new_def
404
+ end
405
+
406
+
407
+ # Lookup a pre-defined argument (created earlier via Argument#register),
408
+ # and add it to this arguments definition.
409
+ #
410
+ # @see Argument#register
411
+ #
412
+ # @param lookup_key [String, Symbol] The key under which the pre-defined
413
+ # argument was registered.
414
+ # @param desc [String] An optional override for the argument description
415
+ # for this use of the pre-defined argument.
416
+ # @param opts [Hash] An options hash for those select properties that
417
+ # can be overridden on a pre-defined argument.
418
+ # @option opts [String] :description The argument description for this
419
+ # use of the pre-defined argument.
420
+ # @option opts [String] :usage_break The usage break for this use of
421
+ # the pre-defined argument.
422
+ # @option opts [Boolean] :required Whether this argument is a required
423
+ # (i.e. mandatory) argument.
424
+ # @option opts [String] :default The default value for the argument,
425
+ # returned in the command-line parse results if no other value is
426
+ # specified.
427
+ def predefined_arg(lookup_key, opts = {})
428
+ # TODO: walk ancestor chain looking at pre-defined arg scopes
429
+ arg = (self.predefined_args && self.predefined_args.key_used?(lookup_key)) ||
430
+ Argument.lookup(lookup_key)
431
+ arg.short_key = opts[:short_key] if opts.has_key?(:short_key)
432
+ arg.description = opts[:description] if opts.has_key?(:description)
433
+ arg.usage_break = opts[:usage_break] if opts.has_key?(:usage_break)
434
+ arg.required = opts[:required] if opts.has_key?(:required)
435
+ arg.default = opts[:default] if opts.has_key?(:default)
436
+ arg.on_parse = opts[:on_parse] if opts.has_key?(:on_parse)
437
+ self << arg
438
+ end
439
+
440
+
441
+ # Individual arguments are optional, but exactly one of +keys+ arguments
442
+ # is required.
443
+ def require_one_of(*keys)
444
+ @require_set << [:one, keys.map{ |k| self[k] }]
445
+ end
446
+
447
+
448
+ # Individual arguments are optional, but at least one of +keys+ arguments
449
+ # is required.
450
+ def require_any_of(*keys)
451
+ @require_set << [:any, keys.map{ |k| self[k] }]
452
+ end
453
+
454
+
455
+ # True if at least one argument is required out of multiple optional args.
456
+ def requires_some?
457
+ @require_set.size > 0
458
+ end
459
+
460
+
461
+ # @return [Parser] a Parser instance that can be used to parse this
462
+ # command-line Definition.
463
+ def parser
464
+ @parser ||= Parser.new(self)
465
+ end
466
+
467
+
468
+ # Parse the +args+ array of arguments using this command-line definition.
469
+ #
470
+ # @param args [Array, String] an array of arguments, or a String representing
471
+ # the command-line that is to be parsed.
472
+ # @return [OpenStruct, false] if successful, an OpenStruct object with all
473
+ # arguments defined as accessors, and the parsed or default values for each
474
+ # argument as values. If unsuccessful, returns false indicating a parse
475
+ # failure.
476
+ # @see Parser#parse, Parser#errors, Parser#show_usage, Parser#show_help
477
+ def parse(args = ARGV)
478
+ parser.parse(args)
479
+ end
480
+
481
+
482
+ # Return an array of parse errors.
483
+ # @see Parser#errors
484
+ def errors
485
+ parser.errors
486
+ end
487
+
488
+
489
+ # Whether user indicated they would like help on usage.
490
+ # @see Parser#show_usage
491
+ def show_usage?
492
+ parser.show_usage?
493
+ end
494
+
495
+
496
+ # Whether user indicated they would like help on supported arguments.
497
+ # @see Parser#show_help
498
+ def show_help?
499
+ parser.show_help?
500
+ end
501
+
502
+
503
+ # Validates the supplied +args+ Hash object, verifying that any argument
504
+ # set requirements have been satisfied. Returns an array of error
505
+ # messages for each set requirement that is not satisfied.
506
+ #
507
+ # @param args [Hash] a Hash containing the keys and values identified
508
+ # by the parser.
509
+ # @return [Array] a list of errors for any argument requirements that
510
+ # have not been satisfied.
511
+ def validate_requirements(args)
512
+ errors = []
513
+ @require_set.each do |req, set|
514
+ count = set.count{ |arg| args.has_key?(arg.key) && args[arg.key] }
515
+ case req
516
+ when :one
517
+ if count == 0
518
+ errors << "No argument has been specified for one of: #{set.join(', ')}"
519
+ elsif count > 1
520
+ errors << "Only one argument can been specified from: #{set.join(', ')}"
521
+ end
522
+ when :any
523
+ if count == 0
524
+ errors << "At least one of the arguments must be specified from: #{set.join(', ')}"
525
+ end
526
+ end
527
+ end
528
+ errors
529
+ end
530
+
320
531
 
321
532
  # Generates a usage display string
322
533
  def show_usage(out = STDERR, width = 80)
323
534
  lines = ['']
324
- pos_args = positional_args
325
- opt_args = size - pos_args.size
326
- usage_args = pos_args.map(&:to_use)
535
+ usage_args = []
536
+ usage_args.concat(positional_args.map(&:to_use))
537
+ opt_args = size - usage_args.size
327
538
  usage_args << (requires_some? ? 'OPTIONS' : '[OPTIONS]') if opt_args > 0
328
539
  usage_args << rest_args.to_use if rest_args?
329
540
  lines.concat(wrap_text("USAGE: #{RUBY_ENGINE} #{$0} #{usage_args.join(' ')}", width))
@@ -348,6 +559,9 @@ module ArgParser
348
559
  if purpose
349
560
  lines.concat(wrap_text(purpose, width))
350
561
  lines << ''
562
+ end
563
+ if copyright
564
+ lines.concat(wrap_text("Copyright (c) #{copyright}", width))
351
565
  lines << ''
352
566
  end
353
567
 
@@ -355,14 +569,15 @@ module ArgParser
355
569
  lines << '-----'
356
570
  pos_args = positional_args
357
571
  opt_args = size - pos_args.size
358
- usage_args = pos_args.map(&:to_use)
572
+ usage_args = []
573
+ usage_args.concat(pos_args.map(&:to_use))
359
574
  usage_args << (requires_some? ? 'OPTIONS' : '[OPTIONS]') if opt_args > 0
360
575
  usage_args << rest_args.to_use if rest_args?
361
576
  lines.concat(wrap_text(" #{RUBY_ENGINE} #{$0} #{usage_args.join(' ')}", width))
362
577
  lines << ''
363
578
 
364
579
  if positional_args?
365
- max = positional_args.map{ |a| a.to_s.length }.max
580
+ max = positional_args.map{ |arg| arg.to_s.length }.max
366
581
  pos_args = positional_args
367
582
  pos_args << rest_args if rest_args?
368
583
  pos_args.each do |arg|
@@ -371,25 +586,48 @@ module ArgParser
371
586
  lines << arg.usage_break
372
587
  end
373
588
  desc = arg.description
374
- desc << "\n[Default: #{arg.default}]" unless arg.default.nil?
589
+ desc += "\n[Default: #{arg.default}]" unless arg.default.nil?
375
590
  wrap_text(desc, width - max - 6).each_with_index do |line, i|
376
591
  lines << " %-#{max}s %s" % [[arg.to_s][i], line]
377
592
  end
378
593
  end
379
594
  lines << ''
380
595
  end
596
+ if command_args?
597
+ max = command_args.reduce(0) do |max, cmd_arg|
598
+ m = cmd_arg.commands.map{ |_, arg| arg.to_s.length }.max
599
+ m > max ? m : max
600
+ end
601
+ command_args.each do |cmd_arg|
602
+ lines << ''
603
+ lines << "#{cmd_arg.to_use}S"
604
+ lines << '--------'
605
+ cmd_arg.commands.each do |_, arg|
606
+ if arg.usage_break
607
+ lines << ''
608
+ lines << arg.usage_break
609
+ end
610
+ desc = arg.description
611
+ wrap_text(desc, width - max - 6).each_with_index do |line, i|
612
+ lines << " %-#{max}s %s" % [[arg.to_s][i], line]
613
+ end
614
+ end
615
+ lines << ''
616
+ end
617
+ end
618
+
381
619
  if non_positional_args?
382
620
  lines << ''
383
621
  lines << 'OPTIONS'
384
622
  lines << '-------'
385
- max = non_positional_args.map{ |a| a.to_use.length }.max
623
+ max = non_positional_args.map{ |arg| arg.to_use.length }.max
386
624
  non_positional_args.each do |arg|
387
625
  if arg.usage_break
388
626
  lines << ''
389
627
  lines << arg.usage_break
390
628
  end
391
629
  desc = arg.description
392
- desc << "\n[Default: #{arg.default}]" unless arg.default.nil?
630
+ desc += "\n[Default: #{arg.default}]" unless arg.default.nil?
393
631
  wrap_text(desc, width - max - 6).each_with_index do |line, i|
394
632
  lines << " %-#{max}s %s" % [[arg.to_use][i], line]
395
633
  end
@@ -461,5 +699,6 @@ module ArgParser
461
699
 
462
700
  end
463
701
 
702
+
464
703
  end
465
704
 
@@ -41,6 +41,7 @@ module ArgParser
41
41
  @args_def ||= ArgParser::Definition.new
42
42
  end
43
43
 
44
+
44
45
  # Returns true if any arguments have been defined
45
46
  def args_defined?
46
47
  @args_def && @args_def.args.size > 0
@@ -64,6 +65,20 @@ module ArgParser
64
65
  args_def.purpose = desc
65
66
  end
66
67
 
68
+ # Sets the copyright notice to be displayed on the help screen or on
69
+ # startup.
70
+ def copyright(txt)
71
+ args_def.copyright = txt
72
+ end
73
+
74
+ # Define a new command argument.
75
+ # @see CommandArgument#initialize
76
+ def command_arg(key, desc, opts = {}, &block)
77
+ opts.merge!(@arg_opts){ |k, e, n| e || n } if @arg_opts
78
+ @arg_opts = nil
79
+ args_def.command_arg(key, desc, opts, &block)
80
+ end
81
+
67
82
  # Define a new positional argument.
68
83
  # @see PositionalArgument#initialize
69
84
  def positional_arg(key, desc, opts = {}, &block)
@@ -96,6 +111,26 @@ module ArgParser
96
111
  args_def.rest_arg(key, desc, opts, &block)
97
112
  end
98
113
 
114
+
115
+ # Define a set of pre-defined arguments for later use
116
+ def define_args(label = nil, &block)
117
+ pre_def_arg_scope = ArgumentScope.new(label || "Predefined args")
118
+ pre_def_arg_scope.instance_eval(&block)
119
+ pre_def_arg_scope
120
+ end
121
+
122
+ # Set the ArgumentScope to use for looking up pre-defined arguments
123
+ def with_predefined_args(scope, &blk)
124
+ if block_given?
125
+ pre_defs = args_def.predefined_args
126
+ args_def.predefined_args = scope
127
+ yield
128
+ args_def.predefined_args = pre_defs
129
+ else
130
+ args_def.predefined_args = scope
131
+ end
132
+ end
133
+
99
134
  # Use a pre-defined argument.
100
135
  # @see Definition#predefined_arg
101
136
  def predefined_arg(lookup_key, desc = nil, opts = {})
@@ -40,11 +40,27 @@ module ArgParser
40
40
  end
41
41
 
42
42
 
43
- # Parse the specified Array[String] of +tokens+, or ARGV if +tokens+ is
44
- # nil. Returns false if unable to parse successfully, or an OpenStruct
45
- # with accessors for every defined argument. Arguments whose values are
46
- # not specified will contain the agument default value, or nil if no
47
- # default is specified.
43
+ # Parse the specified Array<String> of +tokens+, or ARGV if +tokens+ is
44
+ # nil. If the parse is successful, an OpenStruct object is returned with
45
+ # values for all defined arguments as members of the OpenStruct.
46
+ # If parsing is not succssful, it may be due to parse errors or because
47
+ # a help flag was encountered. The +show_usage+ or +show_help+ flags
48
+ # will be set (the former in the case of parse errors, and the latter if
49
+ # help was requested), as well as the +errors+ property if there were parse
50
+ # errors.
51
+ #
52
+ # @note After parse has been called, the argument Definition used by the
53
+ # parser may have been replaced if a command argument value was specified.
54
+ # This new definition can be used to show context-specific help or usage
55
+ # details.
56
+ #
57
+ # @param tokens [Array<String>] The tokens to be parsed using the
58
+ # argument definition set for this parser. If not specified, defaults
59
+ # to ARGV.
60
+ # @return [False|OpenStruct] false if unable to parse successfully, or
61
+ # an OpenStruct with accessors for every defined argument if the parse
62
+ # was successful. Arguments whose values are not specified will contain
63
+ # the agument default value, or nil if no default is specified.
48
64
  def parse(tokens = ARGV)
49
65
  @show_usage = nil
50
66
  @show_help = nil
@@ -73,8 +89,13 @@ module ArgParser
73
89
  # Evaluate the list of values in +tokens+, and classify them as either
74
90
  # keyword/value pairs, or positional arguments. Ideally this would be
75
91
  # done without any reference to the defined arguments, but unfortunately
76
- # a keyword arg cannot be distinguished from a flag arg followed by a
77
- # positional arg without the context of what arguments are expected.
92
+ # there are a couple of issues that require context to be able to handle
93
+ # properly:
94
+ # - a keyword arg cannot be distinguished from a flag arg followed by a
95
+ # positional arg without the context of what arguments are expected.
96
+ # - command arguments will normally have their own set of additional
97
+ # arguments that must be added once we know which command has been
98
+ # entered.
78
99
  def classify_tokens(tokens)
79
100
  if tokens.is_a?(String)
80
101
  require 'csv'
@@ -86,37 +107,68 @@ module ArgParser
86
107
  rest_vals = []
87
108
 
88
109
  arg = nil
110
+ pos_args = @definition.positional_args
89
111
  tokens.each_with_index do |token, i|
112
+ if token.downcase == 'help' && i == 0
113
+ # Accept 'help' as the first token
114
+ @show_help = true
115
+ next
116
+ end
90
117
  case token
91
118
  when '/?', '-?', '--help'
92
119
  @show_help = true
93
120
  when /^[-\/]([a-z0-9]+)$/i
121
+ # One or more short keys
94
122
  $1.to_s.each_char do |sk|
95
123
  kw_vals[arg] = nil if arg
96
124
  arg = @definition[sk]
97
125
  if FlagArgument === arg
98
126
  kw_vals[arg] = true
99
127
  arg = nil
128
+ elsif PositionalArgument == arg
129
+ pos_args.delete(arg)
100
130
  end
101
131
  end
102
- when /^--(no-)?(.+)/i
132
+ when /^--(no[nt]?-)?(.+)/i
133
+ # Long key
103
134
  kw_vals[arg] = nil if arg
104
135
  arg = @definition[$2]
105
136
  if FlagArgument === arg || (KeywordArgument === arg && $1)
106
137
  kw_vals[arg] = $1 ? false : true
107
138
  arg = nil
139
+ elsif PositionalArgument === arg
140
+ pos_args.delete(arg)
108
141
  end
109
142
  when '--'
110
143
  # All subsequent values are rest args
111
144
  kw_vals[arg] = nil if arg
145
+ unless rest_vals.empty?
146
+ self.errors << "Too many positional arguments supplied before -- token"
147
+ end
112
148
  rest_vals = tokens[(i + 1)..-1]
113
149
  break
114
150
  else
115
151
  if arg
116
152
  kw_vals[arg] = token
153
+ elsif pos_args.size > 0
154
+ arg = pos_args.shift
155
+ if CommandArgument === arg
156
+ if cmd_inst = arg[token]
157
+ # Merge command's arg set with the current definition
158
+ @definition = @definition.collapse(cmd_inst)
159
+ # Insert command positional arguments
160
+ pos_args.insert(0, *cmd_inst.argument_scope.positional_args)
161
+ pos_vals << cmd_inst.command_value
162
+ else
163
+ self.errors << "'#{token}' is not a valid value for #{arg}; valid values are: #{
164
+ arg.commands.keys.join(', ')}"
165
+ end
166
+ arg = nil # Commands can't be sensitive
167
+ else
168
+ pos_vals << token
169
+ end
117
170
  else
118
- pos_vals << token
119
- arg = @definition.positional_args[i]
171
+ rest_vals << token
120
172
  end
121
173
  tokens[i] = '******' if arg && arg.sensitive?
122
174
  arg = nil
@@ -154,11 +206,14 @@ module ArgParser
154
206
  end
155
207
 
156
208
  # Process rest values
157
- if rest_arg = @definition.rest_args
158
- result[rest_arg.key] = process_arg_val(rest_arg, rest_vals, result)
159
- elsif rest_vals.size > 0
160
- self.errors << "#{rest_vals.size} rest #{rest_vals.size == 1 ? 'value' : 'values'} #{
161
- rest_vals.size == 1 ? 'was' : 'were'} supplied, but no rest argument is defined"
209
+ if rest_vals.size > 0
210
+ if rest_arg = @definition.rest_args
211
+ result[rest_arg.key] = process_arg_val(rest_arg, rest_vals, result)
212
+ else
213
+ self.errors << "#{rest_vals.size} rest #{rest_vals.size == 1 ? 'value' : 'values'} #{
214
+ rest_vals.size == 1 ? 'was' : 'were'} supplied (#{rest_vals.join(', ')
215
+ }), but no rest argument is defined"
216
+ end
162
217
  end
163
218
 
164
219
  # Default unspecified arguments
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: arg-parser
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Adam Gardiner
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-09-13 00:00:00.000000000 Z
11
+ date: 2019-07-04 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: |2
14
14
  ArgParser is a simple, yet powerful command-line argument parser, with
@@ -19,13 +19,13 @@ executables: []
19
19
  extensions: []
20
20
  extra_rdoc_files: []
21
21
  files:
22
- - README.md
23
22
  - LICENSE
23
+ - README.md
24
+ - lib/arg-parser.rb
24
25
  - lib/arg-parser/argument.rb
25
26
  - lib/arg-parser/definition.rb
26
27
  - lib/arg-parser/dsl.rb
27
28
  - lib/arg-parser/parser.rb
28
- - lib/arg-parser.rb
29
29
  - lib/arg_parser.rb
30
30
  homepage: https://github.com/agardiner/arg-parser
31
31
  licenses: []
@@ -36,17 +36,17 @@ require_paths:
36
36
  - lib
37
37
  required_ruby_version: !ruby/object:Gem::Requirement
38
38
  requirements:
39
- - - '>='
39
+ - - ">="
40
40
  - !ruby/object:Gem::Version
41
41
  version: '0'
42
42
  required_rubygems_version: !ruby/object:Gem::Requirement
43
43
  requirements:
44
- - - '>='
44
+ - - ">="
45
45
  - !ruby/object:Gem::Version
46
46
  version: '0'
47
47
  requirements: []
48
48
  rubyforge_project:
49
- rubygems_version: 2.0.14.1
49
+ rubygems_version: 2.5.2.3
50
50
  signing_key:
51
51
  specification_version: 4
52
52
  summary: ArgParser is a simple, yet powerful, command-line argument (option) parser