trollop 2.1.2 → 2.1.3

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: dbee7d87ff54b18dc15e986c58e8a9f511dda7d1
4
- data.tar.gz: 6fc068202fc2863d9d851931cc67718e8c0b4bd1
3
+ metadata.gz: eceb56ea9f2bee171cbc8a7b88f4053de3024256
4
+ data.tar.gz: b275e0df3037f69de67475f9155b26e8e8a0cad4
5
5
  SHA512:
6
- metadata.gz: 4d79f559d36748e03c32069363d2d2415f341e13ffd5fb6165fb2e62dbfc7a8af70ae164d1c19f02d0be0ab056de2d0e6e15f6319b8f5a760087357db92d21cd
7
- data.tar.gz: 4f0db3cd5cc44e6ff4e23abbc91a0175d9d0132af400cf13890696a9b68ccb8dadcef7b94f9bb3b1576cba6d549cf9b5d28a0c9a6635793fd28553524f641909
6
+ metadata.gz: f71c677c66f414b2e6fc4b1d41c815df416efd57feef1e5f1ada73ff1041025bf87ebb23cd679241ecb2aadea2dbe06976aa7ae3eeabee346de6884852b0bb38
7
+ data.tar.gz: d2ff306c1daa19c6f6026f7cd37e4f2165664878c638e1d9112da400503196be976b779d61edcee68688ab9efe8539a0e206c1bd6766b5caa0f0f6aeef21be0f
@@ -1,8 +1,12 @@
1
1
  language: ruby
2
+ sudo: false
2
3
  rvm:
3
- - "1.8.7"
4
- - "1.9.3"
5
4
  - "2.2"
6
- - ruby-head
5
+ - "2.3.4"
6
+ - "2.6"
7
7
  - jruby-head
8
8
  gemfile: Gemfile.travis
9
+ matrix:
10
+ allow_failures:
11
+ - rvm: jruby-head
12
+ fast_finish: true
@@ -1,5 +1,8 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
+ # these are needed by coveralls
4
+ # if this were in Gemfile, gemnasium thinks the mime-types is hardcoded
5
+ # to an older version and complains. They are using regex's to parse ruby
3
6
  if RUBY_VERSION < "1.9"
4
7
  gem 'rest-client', '< 1.7'
5
8
  gem 'mime-types', '~> 1.16'
@@ -1,10 +1,20 @@
1
- == 2.1.2 / 2015-03-10
1
+ == [2.1.3] / 2018-07-05
2
+
3
+ * Refactor each option type into subclasses of Option. Define a registry for the registration of each option. This makes the code more modular and facilitates extension by allowing additional Option subclasses. (thanks @clxy)
4
+ * Fixed implementation of ignore_invalid_options. (thanks @metcalf)
5
+ * Various warning cleanup for ruby 2.1, 2.3, etc. (thanks @nanobowers)
6
+ * Trollop.die can now accept an error code.
7
+ * fixed default (thanks @nanobowers)
8
+ * Change from ruby license to MIT license in the code.
9
+
10
+ == [2.1.2] / 2015-03-10
2
11
  * loosen mime-types requirements (for better ruby 1.8.7 support)
3
12
  * use io/console gem instead of curses (for better jruby support)
4
13
  * fix parsing bug when chronic gem is not available
5
14
  * allow default array to be empty if a type is specified
6
15
  * better specified license and better spec coverage
7
- == 2.1.1 / 2015-01-03
16
+
17
+ == [2.1.1] / 2015-01-03
8
18
  * Remove curses as a hard dependency. It is optional. This can leverage the gem if it is present.
9
19
  * Fix ruby -w warnings
10
20
 
@@ -148,3 +158,7 @@
148
158
 
149
159
  == 1.0 / 2007-01-29
150
160
  * Initial release.
161
+
162
+ [2.1.3]: https://github.com/ManageIQ/trollop/compare/v2.1.2...v2.1.3
163
+ [2.1.2]: https://github.com/ManageIQ/trollop/compare/v2.1.1...v2.1.2
164
+ [2.1.1]: https://github.com/ManageIQ/trollop/compare/v2.1.0...v2.1.1
data/README.md CHANGED
@@ -8,8 +8,11 @@ http://manageiq.github.io/trollop/
8
8
  [![Coverage Status](http://img.shields.io/coveralls/ManageIQ/trollop.svg)](https://coveralls.io/r/ManageIQ/trollop)
9
9
  [![Dependency Status](https://gemnasium.com/ManageIQ/trollop.svg)](https://gemnasium.com/ManageIQ/trollop)
10
10
 
11
- Documentation quickstart: See Trollop.options and then Trollop::Parser#opt.
12
- Also see the examples at http://manageiq.github.io/trollop/.
11
+ ## Documentation
12
+
13
+ - Quickstart: See `Trollop.options` and then `Trollop::Parser#opt`.
14
+ - Examples: http://manageiq.github.io/trollop/.
15
+ - Wiki: http://github.com/ManageIQ/trollop/wiki
13
16
 
14
17
  ## Description
15
18
 
@@ -54,4 +57,4 @@ Copyright &copy; 2008-2014 [William Morgan](http://masanjin.net/).
54
57
 
55
58
  Copyright &copy; 2014 Red Hat, Inc.
56
59
 
57
- Trollop is released under the [MIT License][http://www.opensource.org/licenses/MIT].
60
+ Trollop is released under the [MIT License](http://www.opensource.org/licenses/MIT).
data/Rakefile CHANGED
@@ -5,7 +5,7 @@ task :default => :test
5
5
 
6
6
  Rake::TestTask.new do |t|
7
7
  t.libs << 'test'
8
- t.pattern = "test/test_*.rb"
8
+ t.pattern = "test/**/*_test.rb"
9
9
  end
10
10
 
11
11
  begin
@@ -1,24 +1,35 @@
1
1
  # lib/trollop.rb -- trollop command-line processing library
2
2
  # Copyright (c) 2008-2014 William Morgan.
3
3
  # Copyright (c) 2014 Red Hat, Inc.
4
- # trollop is licensed under the same terms as Ruby.
4
+ # trollop is licensed under the MIT license.
5
5
 
6
6
  require 'date'
7
7
 
8
8
  module Trollop
9
+ # note: this is duplicated in gemspec
10
+ # please change over there too
9
11
  VERSION = "2.1.2"
10
12
 
11
13
  ## Thrown by Parser in the event of a commandline error. Not needed if
12
14
  ## you're using the Trollop::options entry.
13
- class CommandlineError < StandardError; end
15
+ class CommandlineError < StandardError
16
+ attr_reader :error_code
17
+
18
+ def initialize(msg, error_code = nil)
19
+ super(msg)
20
+ @error_code = error_code
21
+ end
22
+ end
14
23
 
15
24
  ## Thrown by Parser if the user passes in '-h' or '--help'. Handled
16
25
  ## automatically by Trollop#options.
17
- class HelpNeeded < StandardError; end
26
+ class HelpNeeded < StandardError
27
+ end
18
28
 
19
29
  ## Thrown by Parser if the user passes in '-v' or '--version'. Handled
20
30
  ## automatically by Trollop#options.
21
- class VersionNeeded < StandardError; end
31
+ class VersionNeeded < StandardError
32
+ end
22
33
 
23
34
  ## Regex for floating point numbers
24
35
  FLOAT_RE = /^-?((\d+(\.\d+)?)|(\.\d+))([eE][-+]?[\d]+)?$/
@@ -37,24 +48,27 @@ PARAM_RE = /^-(-|\.$|[^\d\.])/
37
48
  ## Trollop::with_standard_exception_handling.
38
49
  class Parser
39
50
 
40
- ## The set of values that indicate a flag option when passed as the
41
- ## +:type+ parameter of #opt.
42
- FLAG_TYPES = [:flag, :bool, :boolean]
43
-
44
- ## The set of values that indicate a single-parameter (normal) option when
45
- ## passed as the +:type+ parameter of #opt.
46
- ##
47
- ## A value of +io+ corresponds to a readable IO resource, including
48
- ## a filename, URI, or the strings 'stdin' or '-'.
49
- SINGLE_ARG_TYPES = [:int, :integer, :string, :double, :float, :io, :date]
50
-
51
- ## The set of values that indicate a multiple-parameter option (i.e., that
52
- ## takes multiple space-separated values on the commandline) when passed as
53
- ## the +:type+ parameter of #opt.
54
- MULTI_ARG_TYPES = [:ints, :integers, :strings, :doubles, :floats, :ios, :dates]
55
-
56
- ## The complete set of legal values for the +:type+ parameter of #opt.
57
- TYPES = FLAG_TYPES + SINGLE_ARG_TYPES + MULTI_ARG_TYPES
51
+ ## The registry is a class-instance-variable map of option aliases to their subclassed Option class.
52
+ @registry = {}
53
+
54
+ ## The Option subclasses are responsible for registering themselves using this function.
55
+ def self.register(lookup, klass)
56
+ @registry[lookup.to_sym] = klass
57
+ end
58
+
59
+ ## Gets the class from the registry.
60
+ ## Can be given either a class-name, e.g. Integer, a string, e.g "integer", or a symbol, e.g :integer
61
+ def self.registry_getopttype(type)
62
+ return nil unless type
63
+ if type.respond_to?(:name)
64
+ type = type.name
65
+ lookup = type.downcase.to_sym
66
+ else
67
+ lookup = type.to_sym
68
+ end
69
+ raise ArgumentError, "Unsupported argument type '#{type}', registry lookup '#{lookup}'" unless @registry.has_key?(lookup)
70
+ return @registry[lookup].new
71
+ end
58
72
 
59
73
  INVALID_SHORT_ARG_REGEX = /[\d-]/ #:nodoc:
60
74
 
@@ -71,7 +85,7 @@ class Parser
71
85
  attr_accessor :ignore_invalid_options
72
86
 
73
87
  ## Initializes the parser, and instance-evaluates any block given.
74
- def initialize *a, &b
88
+ def initialize(*a, &b)
75
89
  @version = nil
76
90
  @leftovers = []
77
91
  @specs = {}
@@ -81,8 +95,11 @@ class Parser
81
95
  @constraints = []
82
96
  @stop_words = []
83
97
  @stop_on_unknown = false
98
+ @educate_on_error = false
99
+ @synopsis = nil
100
+ @usage = nil
84
101
 
85
- #instance_eval(&b) if b # can't take arguments
102
+ # instance_eval(&b) if b # can't take arguments
86
103
  cloaker(&b).bind(self).call(*a) if b
87
104
  end
88
105
 
@@ -94,7 +111,7 @@ class Parser
94
111
  ## Takes the following optional arguments:
95
112
  ##
96
113
  ## [+:long+] Specify the long form of the argument, i.e. the form with two dashes. If unspecified, will be automatically derived based on the argument name by turning the +name+ option into a string, and replacing any _'s by -'s.
97
- ## [+:short+] Specify the short form of the argument, i.e. the form with one dash. If unspecified, will be automatically derived from +name+.
114
+ ## [+:short+] Specify the short form of the argument, i.e. the form with one dash. If unspecified, will be automatically derived from +name+. Use :none: to not have a short value.
98
115
  ## [+:type+] Require that the argument take a parameter or parameters of type +type+. For a single parameter, the value can be a member of +SINGLE_ARG_TYPES+, or a corresponding Ruby class (e.g. +Integer+ for +:int+). For multiple-argument parameters, the value can be any member of +MULTI_ARG_TYPES+ constant. If unset, the default argument type is +:flag+, meaning that the argument does not take a parameter. The specification of +:type+ is not necessary if a +:default+ is given.
99
116
  ## [+:default+] Set the default value for an argument. Without a default value, the hash returned by #parse (and thus Trollop::options) will have a +nil+ value for this key unless the argument is given on the commandline. The argument type is derived automatically from the class of the default value given, so specifying a +:type+ is not necessary if a +:default+ is given. (But see below for an important caveat when +:multi+: is specified too.) If the argument is a flag, and the default is set to +true+, then if it is specified on the the commandline the value will be +false+.
100
117
  ## [+:required+] If set to +true+, the argument must be provided on the commandline.
@@ -126,147 +143,58 @@ class Parser
126
143
  ## If you want a multi-value, multi-occurrence argument with a default
127
144
  ## value, you must specify +:type+ as well.
128
145
 
129
- def opt name, desc="", opts={}, &b
130
- raise ArgumentError, "you already have an argument named '#{name}'" if @specs.member? name
131
-
132
- ## fill in :type
133
- opts[:type] = # normalize
134
- case opts[:type]
135
- when :boolean, :bool; :flag
136
- when :integer; :int
137
- when :integers; :ints
138
- when :double; :float
139
- when :doubles; :floats
140
- when Class
141
- case opts[:type].name
142
- when 'TrueClass', 'FalseClass'; :flag
143
- when 'String'; :string
144
- when 'Integer'; :int
145
- when 'Float'; :float
146
- when 'IO'; :io
147
- when 'Date'; :date
148
- else
149
- raise ArgumentError, "unsupported argument type '#{opts[:type].class.name}'"
150
- end
151
- when nil; nil
152
- else
153
- raise ArgumentError, "unsupported argument type '#{opts[:type]}'" unless TYPES.include?(opts[:type])
154
- opts[:type]
155
- end
156
-
157
- ## for options with :multi => true, an array default doesn't imply
158
- ## a multi-valued argument. for that you have to specify a :type
159
- ## as well. (this is how we disambiguate an ambiguous situation;
160
- ## see the docs for Parser#opt for details.)
161
- disambiguated_default = if opts[:multi] && opts[:default].is_a?(Array) && !opts[:type]
162
- opts[:default].first
163
- else
164
- opts[:default]
165
- end
166
-
167
- type_from_default =
168
- case disambiguated_default
169
- when Integer; :int
170
- when Numeric; :float
171
- when TrueClass, FalseClass; :flag
172
- when String; :string
173
- when IO; :io
174
- when Date; :date
175
- when Array
176
- if opts[:default].empty?
177
- if opts[:type]
178
- raise ArgumentError, "multiple argument type must be plural" unless MULTI_ARG_TYPES.include?(opts[:type])
179
- nil
180
- else
181
- raise ArgumentError, "multiple argument type cannot be deduced from an empty array for '#{opts[:default][0].class.name}'"
182
- end
183
- else
184
- case opts[:default][0] # the first element determines the types
185
- when Integer; :ints
186
- when Numeric; :floats
187
- when String; :strings
188
- when IO; :ios
189
- when Date; :dates
190
- else
191
- raise ArgumentError, "unsupported multiple argument type '#{opts[:default][0].class.name}'"
192
- end
193
- end
194
- when nil; nil
195
- else
196
- raise ArgumentError, "unsupported argument type '#{opts[:default].class.name}'"
197
- end
198
-
199
- raise ArgumentError, ":type specification and default type don't match (default type is #{type_from_default})" if opts[:type] && type_from_default && opts[:type] != type_from_default
200
-
201
- opts[:type] = opts[:type] || type_from_default || :flag
202
-
203
- ## fill in :long
204
- opts[:long] = opts[:long] ? opts[:long].to_s : name.to_s.gsub("_", "-")
205
- opts[:long] = case opts[:long]
206
- when /^--([^-].*)$/; $1
207
- when /^[^-]/; opts[:long]
208
- else; raise ArgumentError, "invalid long option name #{opts[:long].inspect}"
209
- end
210
- raise ArgumentError, "long option name #{opts[:long].inspect} is already taken; please specify a (different) :long" if @long[opts[:long]]
211
-
212
- ## fill in :short
213
- opts[:short] = opts[:short].to_s if opts[:short] && opts[:short] != :none
214
- opts[:short] = case opts[:short]
215
- when /^-(.)$/; $1
216
- when nil, :none, /^.$/; opts[:short]
217
- else raise ArgumentError, "invalid short option name '#{opts[:short].inspect}'"
218
- end
219
-
220
- if opts[:short]
221
- raise ArgumentError, "short option name #{opts[:short].inspect} is already taken; please specify a (different) :short" if @short[opts[:short]]
222
- raise ArgumentError, "a short option name can't be a number or a dash" if opts[:short] =~ INVALID_SHORT_ARG_REGEX
223
- end
224
-
225
- ## fill in :default for flags
226
- opts[:default] = false if opts[:type] == :flag && opts[:default].nil?
227
-
228
- ## autobox :default for :multi (multi-occurrence) arguments
229
- opts[:default] = [opts[:default]] if opts[:default] && opts[:multi] && !opts[:default].is_a?(Array)
230
-
231
- ## fill in :multi
232
- opts[:multi] ||= false
146
+ def opt(name, desc = "", opts = {}, &b)
233
147
  opts[:callback] ||= b if block_given?
234
148
  opts[:desc] ||= desc
235
- @long[opts[:long]] = name
236
- @short[opts[:short]] = name if opts[:short] && opts[:short] != :none
237
- @specs[name] = opts
238
- @order << [:opt, name]
149
+
150
+ o = Option.create(name, desc, opts)
151
+
152
+ raise ArgumentError, "you already have an argument named '#{name}'" if @specs.member? o.name
153
+ raise ArgumentError, "long option name #{o.long.inspect} is already taken; please specify a (different) :long" if @long[o.long]
154
+ raise ArgumentError, "short option name #{o.short.inspect} is already taken; please specify a (different) :short" if @short[o.short]
155
+ @long[o.long] = o.name
156
+ @short[o.short] = o.name if o.short?
157
+ @specs[o.name] = o
158
+ @order << [:opt, o.name]
239
159
  end
240
160
 
241
161
  ## Sets the version string. If set, the user can request the version
242
162
  ## on the commandline. Should probably be of the form "<program name>
243
163
  ## <version number>".
244
- def version(s = nil); @version = s if s; @version end
164
+ def version(s = nil)
165
+ s ? @version = s : @version
166
+ end
245
167
 
246
168
  ## Sets the usage string. If set the message will be printed as the
247
169
  ## first line in the help (educate) output and ending in two new
248
170
  ## lines.
249
- def usage(s = nil) ; @usage = s if s; @usage end
171
+ def usage(s = nil)
172
+ s ? @usage = s : @usage
173
+ end
250
174
 
251
175
  ## Adds a synopsis (command summary description) right below the
252
176
  ## usage line, or as the first line if usage isn't specified.
253
- def synopsis(s = nil) ; @synopsis = s if s; @synopsis end
177
+ def synopsis(s = nil)
178
+ s ? @synopsis = s : @synopsis
179
+ end
254
180
 
255
181
  ## Adds text to the help display. Can be interspersed with calls to
256
182
  ## #opt to build a multi-section help page.
257
- def banner s; @order << [:text, s] end
258
- alias :text :banner
183
+ def banner(s)
184
+ @order << [:text, s]
185
+ end
186
+ alias_method :text, :banner
259
187
 
260
188
  ## Marks two (or more!) options as requiring each other. Only handles
261
189
  ## undirected (i.e., mutual) dependencies. Directed dependencies are
262
190
  ## better modeled with Trollop::die.
263
- def depends *syms
191
+ def depends(*syms)
264
192
  syms.each { |sym| raise ArgumentError, "unknown option '#{sym}'" unless @specs[sym] }
265
193
  @constraints << [:depends, syms]
266
194
  end
267
195
 
268
196
  ## Marks two (or more!) options as conflicting.
269
- def conflicts *syms
197
+ def conflicts(*syms)
270
198
  syms.each { |sym| raise ArgumentError, "unknown option '#{sym}'" unless @specs[sym] }
271
199
  @constraints << [:conflicts, syms]
272
200
  end
@@ -280,7 +208,7 @@ class Parser
280
208
  ## would be set to the list of subcommands. A subsequent Trollop
281
209
  ## invocation would then be used to parse subcommand options, after
282
210
  ## shifting the subcommand off of ARGV.
283
- def stop_on *words
211
+ def stop_on(*words)
284
212
  @stop_words = [*words].flatten
285
213
  end
286
214
 
@@ -292,21 +220,27 @@ class Parser
292
220
  @stop_on_unknown = true
293
221
  end
294
222
 
223
+ ## Instead of displaying "Try --help for help." on an error
224
+ ## display the usage (via educate)
225
+ def educate_on_error
226
+ @educate_on_error = true
227
+ end
228
+
295
229
  ## Parses the commandline. Typically called by Trollop::options,
296
230
  ## but you can call it directly if you need more control.
297
231
  ##
298
232
  ## throws CommandlineError, HelpNeeded, and VersionNeeded exceptions.
299
- def parse cmdline=ARGV
233
+ def parse(cmdline = ARGV)
300
234
  vals = {}
301
235
  required = {}
302
236
 
303
- opt :version, "Print version and exit" if @version && ! ( @specs[:version] || @long["version"])
237
+ opt :version, "Print version and exit" if @version && ! (@specs[:version] || @long["version"])
304
238
  opt :help, "Show this message" unless @specs[:help] || @long["help"]
305
239
 
306
240
  @specs.each do |sym, opts|
307
- required[sym] = true if opts[:required]
308
- vals[sym] = opts[:default]
309
- vals[sym] = [] if opts[:multi] && !opts[:default] # multi arguments default to [], not nil
241
+ required[sym] = true if opts.required?
242
+ vals[sym] = opts.default
243
+ vals[sym] = [] if opts.multi && !opts.default # multi arguments default to [], not nil
310
244
  end
311
245
 
312
246
  resolve_default_short_options!
@@ -322,17 +256,17 @@ class Parser
322
256
  end
323
257
 
324
258
  sym = case arg
325
- when /^-([^-])$/; @short[$1]
326
- when /^--([^-]\S*)$/; @long[$1] || @long["no-#{$1}"]
327
- else; raise CommandlineError, "invalid argument syntax: '#{arg}'"
259
+ when /^-([^-])$/ then @short[$1]
260
+ when /^--([^-]\S*)$/ then @long[$1] || @long["no-#{$1}"]
261
+ else raise CommandlineError, "invalid argument syntax: '#{arg}'"
328
262
  end
329
263
 
330
264
  sym = nil if arg =~ /--no-/ # explicitly invalidate --no-no- arguments
331
265
 
332
- next 0 if ignore_invalid_options && !sym
266
+ next nil if ignore_invalid_options && !sym
333
267
  raise CommandlineError, "unknown argument '#{arg}'" unless sym
334
268
 
335
- if given_args.include?(sym) && !@specs[sym][:multi]
269
+ if given_args.include?(sym) && !@specs[sym].multi?
336
270
  raise CommandlineError, "option '#{arg}' specified multiple times"
337
271
  end
338
272
 
@@ -344,11 +278,11 @@ class Parser
344
278
  # The block returns the number of parameters taken.
345
279
  num_params_taken = 0
346
280
 
347
- unless params.nil?
348
- if SINGLE_ARG_TYPES.include?(@specs[sym][:type])
281
+ unless params.empty?
282
+ if @specs[sym].single_arg?
349
283
  given_args[sym][:params] << params[0, 1] # take the first parameter
350
284
  num_params_taken = 1
351
- elsif MULTI_ARG_TYPES.include?(@specs[sym][:type])
285
+ elsif @specs[sym].multi_arg?
352
286
  given_args[sym][:params] << params # take all the parameters
353
287
  num_params_taken = params.size
354
288
  end
@@ -368,14 +302,14 @@ class Parser
368
302
 
369
303
  case type
370
304
  when :depends
371
- syms.each { |sym| raise CommandlineError, "--#{@specs[constraint_sym][:long]} requires --#{@specs[sym][:long]}" unless given_args.include? sym }
305
+ syms.each { |sym| raise CommandlineError, "--#{@specs[constraint_sym].long} requires --#{@specs[sym].long}" unless given_args.include? sym }
372
306
  when :conflicts
373
- syms.each { |sym| raise CommandlineError, "--#{@specs[constraint_sym][:long]} conflicts with --#{@specs[sym][:long]}" if given_args.include?(sym) && (sym != constraint_sym) }
307
+ syms.each { |sym| raise CommandlineError, "--#{@specs[constraint_sym].long} conflicts with --#{@specs[sym].long}" if given_args.include?(sym) && (sym != constraint_sym) }
374
308
  end
375
309
  end
376
310
 
377
311
  required.each do |sym, val|
378
- raise CommandlineError, "option --#{@specs[sym][:long]} must be specified" unless given_args.include? sym
312
+ raise CommandlineError, "option --#{@specs[sym].long} must be specified" unless given_args.include? sym
379
313
  end
380
314
 
381
315
  ## parse parameters
@@ -383,40 +317,27 @@ class Parser
383
317
  arg, params, negative_given = given_data.values_at :arg, :params, :negative_given
384
318
 
385
319
  opts = @specs[sym]
386
- if params.empty? && opts[:type] != :flag
387
- raise CommandlineError, "option '#{arg}' needs a parameter" unless opts[:default]
388
- params << (opts[:default].kind_of?(Array) ? opts[:default].clone : [opts[:default]])
320
+ if params.empty? && !opts.flag?
321
+ raise CommandlineError, "option '#{arg}' needs a parameter" unless opts.default
322
+ params << (opts.array_default? ? opts.default.clone : [opts.default])
389
323
  end
390
324
 
391
325
  vals["#{sym}_given".intern] = true # mark argument as specified on the commandline
392
326
 
393
- case opts[:type]
394
- when :flag
395
- vals[sym] = (sym.to_s =~ /^no_/ ? negative_given : !negative_given)
396
- when :int, :ints
397
- vals[sym] = params.map { |pg| pg.map { |p| parse_integer_parameter p, arg } }
398
- when :float, :floats
399
- vals[sym] = params.map { |pg| pg.map { |p| parse_float_parameter p, arg } }
400
- when :string, :strings
401
- vals[sym] = params.map { |pg| pg.map { |p| p.to_s } }
402
- when :io, :ios
403
- vals[sym] = params.map { |pg| pg.map { |p| parse_io_parameter p, arg } }
404
- when :date, :dates
405
- vals[sym] = params.map { |pg| pg.map { |p| parse_date_parameter p, arg } }
406
- end
327
+ vals[sym] = opts.parse(params, negative_given)
407
328
 
408
- if SINGLE_ARG_TYPES.include?(opts[:type])
409
- if opts[:multi] # multiple options, each with a single parameter
329
+ if opts.single_arg?
330
+ if opts.multi? # multiple options, each with a single parameter
410
331
  vals[sym] = vals[sym].map { |p| p[0] }
411
332
  else # single parameter
412
333
  vals[sym] = vals[sym][0][0]
413
334
  end
414
- elsif MULTI_ARG_TYPES.include?(opts[:type]) && !opts[:multi]
335
+ elsif opts.multi_arg? && !opts.multi?
415
336
  vals[sym] = vals[sym][0] # single option, with multiple parameters
416
337
  end
417
338
  # else: multiple options, with multiple parameters
418
339
 
419
- opts[:callback].call(vals[sym]) if opts.has_key?(:callback)
340
+ opts.callback.call(vals[sym]) if opts.callback
420
341
  end
421
342
 
422
343
  ## modify input in place with only those
@@ -426,61 +347,30 @@ class Parser
426
347
 
427
348
  ## allow openstruct-style accessors
428
349
  class << vals
429
- def method_missing(m, *args)
350
+ def method_missing(m, *_args)
430
351
  self[m] || self[m.to_s]
431
352
  end
432
353
  end
433
354
  vals
434
355
  end
435
356
 
436
- def parse_date_parameter param, arg #:nodoc:
437
- begin
438
- begin
439
- require 'chronic'
440
- time = Chronic.parse(param)
441
- rescue LoadError
442
- # chronic is not available
443
- end
444
- time ? Date.new(time.year, time.month, time.day) : Date.parse(param)
445
- rescue ArgumentError
446
- raise CommandlineError, "option '#{arg}' needs a date"
447
- end
448
- end
449
-
450
357
  ## Print the help message to +stream+.
451
- def educate stream=$stdout
358
+ def educate(stream = $stdout)
452
359
  width # hack: calculate it now; otherwise we have to be careful not to
453
360
  # call this unless the cursor's at the beginning of a line.
454
- left = {}
455
- @specs.each do |name, spec|
456
- left[name] =
457
- (spec[:short] && spec[:short] != :none ? "-#{spec[:short]}" : "") +
458
- (spec[:short] && spec[:short] != :none ? ", " : "") + "--#{spec[:long]}" +
459
- case spec[:type]
460
- when :flag; ""
461
- when :int; "=<i>"
462
- when :ints; "=<i+>"
463
- when :string; "=<s>"
464
- when :strings; "=<s+>"
465
- when :float; "=<f>"
466
- when :floats; "=<f+>"
467
- when :io; "=<filename/uri>"
468
- when :ios; "=<filename/uri+>"
469
- when :date; "=<date>"
470
- when :dates; "=<date+>"
471
- end +
472
- (spec[:type] == :flag && spec[:default] ? ", --no-#{spec[:long]}" : "")
473
- end
474
361
 
475
- leftcol_width = left.values.map { |s| s.length }.max || 0
362
+ left = {}
363
+ @specs.each { |name, spec| left[name] = spec.educate }
364
+
365
+ leftcol_width = left.values.map(&:length).max || 0
476
366
  rightcol_start = leftcol_width + 6 # spaces
477
367
 
478
368
  unless @order.size > 0 && @order.first.first == :text
479
369
  command_name = File.basename($0).gsub(/\.[^.]+$/, '')
480
- stream.puts "Usage: #{command_name} #@usage\n" if @usage
481
- stream.puts "#@synopsis\n" if @synopsis
482
- stream.puts if @usage or @synopsis
483
- stream.puts "#@version\n" if @version
370
+ stream.puts "Usage: #{command_name} #{@usage}\n" if @usage
371
+ stream.puts "#{@synopsis}\n" if @synopsis
372
+ stream.puts if @usage || @synopsis
373
+ stream.puts "#{@version}\n" if @version
484
374
  stream.puts "Options:"
485
375
  end
486
376
 
@@ -492,27 +382,8 @@ class Parser
492
382
 
493
383
  spec = @specs[opt]
494
384
  stream.printf " %-#{leftcol_width}s ", left[opt]
495
- desc = spec[:desc] + begin
496
- default_s = case spec[:default]
497
- when $stdout; "<stdout>"
498
- when $stdin; "<stdin>"
499
- when $stderr; "<stderr>"
500
- when Array
501
- spec[:default].join(", ")
502
- else
503
- spec[:default].to_s
504
- end
385
+ desc = spec.description_with_default
505
386
 
506
- if spec[:default]
507
- if spec[:desc] =~ /\.$/
508
- " (Default: #{default_s})"
509
- else
510
- " (default: #{default_s})"
511
- end
512
- else
513
- ""
514
- end
515
- end
516
387
  stream.puts wrap(desc, :width => width - rightcol_start - 1, :prefix => rightcol_start)
517
388
  end
518
389
  end
@@ -521,7 +392,8 @@ class Parser
521
392
  @width ||= if $stdout.tty?
522
393
  begin
523
394
  require 'io/console'
524
- IO.console.winsize.last
395
+ w = IO.console.winsize.last
396
+ w.to_i > 0 ? w : 80
525
397
  rescue LoadError, NoMethodError, Errno::ENOTTY, Errno::EBADF, Errno::EINVAL
526
398
  legacy_width
527
399
  end
@@ -538,7 +410,7 @@ class Parser
538
410
  end
539
411
  private :legacy_width
540
412
 
541
- def wrap str, opts={} # :nodoc:
413
+ def wrap(str, opts = {}) # :nodoc:
542
414
  if str == ""
543
415
  [""]
544
416
  else
@@ -552,75 +424,92 @@ class Parser
552
424
  end
553
425
 
554
426
  ## The per-parser version of Trollop::die (see that for documentation).
555
- def die arg, msg
427
+ def die(arg, msg = nil, error_code = nil)
428
+ msg, error_code = nil, msg if msg.kind_of?(Integer)
556
429
  if msg
557
- $stderr.puts "Error: argument --#{@specs[arg][:long]} #{msg}."
430
+ $stderr.puts "Error: argument --#{@specs[arg].long} #{msg}."
558
431
  else
559
432
  $stderr.puts "Error: #{arg}."
560
433
  end
561
- $stderr.puts "Try --help for help."
562
- exit(-1)
434
+ if @educate_on_error
435
+ $stderr.puts
436
+ educate $stderr
437
+ else
438
+ $stderr.puts "Try --help for help."
439
+ end
440
+ exit(error_code || -1)
563
441
  end
564
442
 
565
443
  private
566
444
 
567
445
  ## yield successive arg, parameter pairs
568
- def each_arg args
446
+ def each_arg(args)
569
447
  remains = []
570
448
  i = 0
571
449
 
572
450
  until i >= args.length
573
- if @stop_words.member? args[i]
574
- return remains += args[i .. -1]
575
- end
451
+ return remains += args[i..-1] if @stop_words.member? args[i]
576
452
  case args[i]
577
453
  when /^--$/ # arg terminator
578
- return remains += args[(i + 1) .. -1]
454
+ return remains += args[(i + 1)..-1]
579
455
  when /^--(\S+?)=(.*)$/ # long argument with equals
580
- yield "--#{$1}", [$2]
456
+ num_params_taken = yield "--#{$1}", [$2]
457
+ if num_params_taken.nil?
458
+ remains << args[i]
459
+ if @stop_on_unknown
460
+ return remains += args[i + 1..-1]
461
+ end
462
+ end
581
463
  i += 1
582
464
  when /^--(\S+)$/ # long argument
583
465
  params = collect_argument_parameters(args, i + 1)
584
- if params.empty?
585
- yield args[i], nil
586
- i += 1
587
- else
588
- num_params_taken = yield args[i], params
589
- unless num_params_taken
590
- if @stop_on_unknown
591
- return remains += args[i + 1 .. -1]
592
- else
593
- remains += params
594
- end
466
+ num_params_taken = yield args[i], params
467
+
468
+ if num_params_taken.nil?
469
+ remains << args[i]
470
+ if @stop_on_unknown
471
+ return remains += args[i + 1..-1]
595
472
  end
596
- i += 1 + num_params_taken
473
+ else
474
+ i += num_params_taken
597
475
  end
476
+ i += 1
598
477
  when /^-(\S+)$/ # one or more short arguments
478
+ short_remaining = ""
599
479
  shortargs = $1.split(//)
600
480
  shortargs.each_with_index do |a, j|
601
481
  if j == (shortargs.length - 1)
602
482
  params = collect_argument_parameters(args, i + 1)
603
- if params.empty?
604
- yield "-#{a}", nil
605
- i += 1
606
- else
607
- num_params_taken = yield "-#{a}", params
608
- unless num_params_taken
609
- if @stop_on_unknown
610
- return remains += args[i + 1 .. -1]
611
- else
612
- remains += params
613
- end
483
+
484
+ num_params_taken = yield "-#{a}", params
485
+ unless num_params_taken
486
+ short_remaining << a
487
+ if @stop_on_unknown
488
+ remains << "-#{short_remaining}"
489
+ return remains += args[i + 1..-1]
614
490
  end
615
- i += 1 + num_params_taken
491
+ else
492
+ i += num_params_taken
616
493
  end
617
494
  else
618
- yield "-#{a}", nil
495
+ unless yield "-#{a}", []
496
+ short_remaining << a
497
+ if @stop_on_unknown
498
+ short_remaining += shortargs[j + 1..-1].join
499
+ remains << "-#{short_remaining}"
500
+ return remains += args[i + 1..-1]
501
+ end
502
+ end
619
503
  end
620
504
  end
505
+
506
+ unless short_remaining.empty?
507
+ remains << "-#{short_remaining}"
508
+ end
509
+ i += 1
621
510
  else
622
511
  if @stop_on_unknown
623
- return remains += args[i .. -1]
512
+ return remains += args[i..-1]
624
513
  else
625
514
  remains << args[i]
626
515
  i += 1
@@ -631,30 +520,7 @@ private
631
520
  remains
632
521
  end
633
522
 
634
- def parse_integer_parameter param, arg
635
- raise CommandlineError, "option '#{arg}' needs an integer" unless param =~ /^-?[\d_]+$/
636
- param.to_i
637
- end
638
-
639
- def parse_float_parameter param, arg
640
- raise CommandlineError, "option '#{arg}' needs a floating-point number" unless param =~ FLOAT_RE
641
- param.to_f
642
- end
643
-
644
- def parse_io_parameter param, arg
645
- case param
646
- when /^(stdin|-)$/i; $stdin
647
- else
648
- require 'open-uri'
649
- begin
650
- open param
651
- rescue SystemCallError => e
652
- raise CommandlineError, "file or url for option '#{arg}' cannot be opened: #{e.message}"
653
- end
654
- end
655
- end
656
-
657
- def collect_argument_parameters args, start_at
523
+ def collect_argument_parameters(args, start_at)
658
524
  params = []
659
525
  pos = start_at
660
526
  while args[pos] && args[pos] !~ PARAM_RE && !@stop_words.member?(args[pos]) do
@@ -667,17 +533,17 @@ private
667
533
  def resolve_default_short_options!
668
534
  @order.each do |type, name|
669
535
  opts = @specs[name]
670
- next if type != :opt || opts[:short]
536
+ next if type != :opt || opts.short
671
537
 
672
- c = opts[:long].split(//).find { |d| d !~ INVALID_SHORT_ARG_REGEX && !@short.member?(d) }
538
+ c = opts.long.split(//).find { |d| d !~ INVALID_SHORT_ARG_REGEX && !@short.member?(d) }
673
539
  if c # found a character to use
674
- opts[:short] = c
540
+ opts.short = c
675
541
  @short[c] = name
676
542
  end
677
543
  end
678
544
  end
679
545
 
680
- def wrap_line str, opts={}
546
+ def wrap_line(str, opts = {})
681
547
  prefix = opts[:prefix] || 0
682
548
  width = opts[:width] || (self.width - 1)
683
549
  start = 0
@@ -691,7 +557,7 @@ private
691
557
  x = str.index(/\s/, start) if x && x < start
692
558
  x || str.length
693
559
  end
694
- ret << ((ret.empty? && !opts[:inner]) ? "" : " " * prefix) + str[start ... nextt]
560
+ ret << ((ret.empty? && !opts[:inner]) ? "" : " " * prefix) + str[start...nextt]
695
561
  start = nextt + 1
696
562
  end
697
563
  ret
@@ -699,7 +565,7 @@ private
699
565
 
700
566
  ## instance_eval but with ability to handle block arguments
701
567
  ## thanks to _why: http://redhanded.hobix.com/inspect/aBlockCostume.html
702
- def cloaker &b
568
+ def cloaker(&b)
703
569
  (class << self; self; end).class_eval do
704
570
  define_method :cloaker_, &b
705
571
  meth = instance_method :cloaker_
@@ -709,6 +575,317 @@ private
709
575
  end
710
576
  end
711
577
 
578
+ class Option
579
+
580
+ attr_accessor :name, :short, :long, :default
581
+ attr_writer :multi_given
582
+
583
+ def initialize
584
+ @long = nil
585
+ @short = nil
586
+ @name = nil
587
+ @multi_given = false
588
+ @hidden = false
589
+ @default = nil
590
+ @optshash = Hash.new()
591
+ end
592
+
593
+ def opts (key)
594
+ @optshash[key]
595
+ end
596
+
597
+ def opts= (o)
598
+ @optshash = o
599
+ end
600
+
601
+ ## Indicates a flag option, which is an option without an argument
602
+ def flag? ; false ; end
603
+ def single_arg?
604
+ !self.multi_arg? && !self.flag?
605
+ end
606
+
607
+ def multi ; @multi_given ; end
608
+ alias multi? multi
609
+
610
+ ## Indicates that this is a multivalued (Array type) argument
611
+ def multi_arg? ; false ; end
612
+ ## note: Option-Types with both multi_arg? and flag? false are single-parameter (normal) options.
613
+
614
+ def array_default? ; self.default.kind_of?(Array) ; end
615
+
616
+ def short? ; short && short != :none ; end
617
+
618
+ def callback ; opts(:callback) ; end
619
+ def desc ; opts(:desc) ; end
620
+
621
+ def required? ; opts(:required) ; end
622
+
623
+ def parse (_paramlist, _neg_given)
624
+ raise NotImplementedError, "parse must be overridden for newly registered type"
625
+ end
626
+
627
+ # provide type-format string. default to empty, but user should probably override it
628
+ def type_format ; "" ; end
629
+
630
+ def educate
631
+ (short? ? "-#{short}, " : "") + "--#{long}" + type_format + (flag? && default ? ", --no-#{long}" : "")
632
+ end
633
+
634
+ ## Format the educate-line description including the default-value(s)
635
+ def description_with_default
636
+ return desc unless default
637
+ default_s = case default
638
+ when $stdout then '<stdout>'
639
+ when $stdin then '<stdin>'
640
+ when $stderr then '<stderr>'
641
+ when Array
642
+ default.join(', ')
643
+ else
644
+ default.to_s
645
+ end
646
+ defword = desc.end_with?('.') ? 'Default' : 'default'
647
+ return "#{desc} (#{defword}: #{default_s})"
648
+ end
649
+
650
+ ## Provide a way to register symbol aliases to the Parser
651
+ def self.register_alias(*alias_keys)
652
+ alias_keys.each do |alias_key|
653
+ # pass in the alias-key and the class
654
+ Parser.register(alias_key, self)
655
+ end
656
+ end
657
+
658
+ ## Factory class methods ...
659
+
660
+ # Determines which type of object to create based on arguments passed
661
+ # to +Trollop::opt+. This is trickier in Trollop, than other cmdline
662
+ # parsers (e.g. Slop) because we allow the +default:+ to be able to
663
+ # set the option's type.
664
+ def self.create(name, desc="", opts={}, settings={})
665
+
666
+ opttype = Trollop::Parser.registry_getopttype(opts[:type])
667
+ opttype_from_default = get_klass_from_default(opts, opttype)
668
+
669
+ raise ArgumentError, ":type specification and default type don't match (default type is #{opttype_from_default.class})" if opttype && opttype_from_default && (opttype.class != opttype_from_default.class)
670
+
671
+ opt_inst = (opttype || opttype_from_default || Trollop::BooleanOption.new)
672
+
673
+ ## fill in :long
674
+ opt_inst.long = handle_long_opt(opts[:long], name)
675
+
676
+ ## fill in :short
677
+ opt_inst.short = handle_short_opt(opts[:short])
678
+
679
+ ## fill in :multi
680
+ multi_given = opts[:multi] || false
681
+ opt_inst.multi_given = multi_given
682
+
683
+ ## fill in :default for flags
684
+ defvalue = opts[:default] || opt_inst.default
685
+
686
+ ## autobox :default for :multi (multi-occurrence) arguments
687
+ defvalue = [defvalue] if defvalue && multi_given && !defvalue.kind_of?(Array)
688
+ opt_inst.default = defvalue
689
+ opt_inst.name = name
690
+ opt_inst.opts = opts
691
+ opt_inst
692
+ end
693
+
694
+ private
695
+
696
+ def self.get_type_from_disdef(optdef, opttype, disambiguated_default)
697
+ if disambiguated_default.is_a? Array
698
+ return(optdef.first.class.name.downcase + "s") if !optdef.empty?
699
+ if opttype
700
+ raise ArgumentError, "multiple argument type must be plural" unless opttype.multi_arg?
701
+ return nil
702
+ else
703
+ raise ArgumentError, "multiple argument type cannot be deduced from an empty array"
704
+ end
705
+ end
706
+ return disambiguated_default.class.name.downcase
707
+ end
708
+
709
+ def self.get_klass_from_default(opts, opttype)
710
+ ## for options with :multi => true, an array default doesn't imply
711
+ ## a multi-valued argument. for that you have to specify a :type
712
+ ## as well. (this is how we disambiguate an ambiguous situation;
713
+ ## see the docs for Parser#opt for details.)
714
+
715
+ disambiguated_default = if opts[:multi] && opts[:default].is_a?(Array) && opttype.nil?
716
+ opts[:default].first
717
+ else
718
+ opts[:default]
719
+ end
720
+
721
+ return nil if disambiguated_default.nil?
722
+ type_from_default = get_type_from_disdef(opts[:default], opttype, disambiguated_default)
723
+ return Trollop::Parser.registry_getopttype(type_from_default)
724
+ end
725
+
726
+ def self.handle_long_opt(lopt, name)
727
+ lopt = lopt ? lopt.to_s : name.to_s.gsub("_", "-")
728
+ lopt = case lopt
729
+ when /^--([^-].*)$/ then $1
730
+ when /^[^-]/ then lopt
731
+ else raise ArgumentError, "invalid long option name #{lopt.inspect}"
732
+ end
733
+ end
734
+
735
+ def self.handle_short_opt(sopt)
736
+ sopt = sopt.to_s if sopt && sopt != :none
737
+ sopt = case sopt
738
+ when /^-(.)$/ then $1
739
+ when nil, :none, /^.$/ then sopt
740
+ else raise ArgumentError, "invalid short option name '#{sopt.inspect}'"
741
+ end
742
+
743
+ if sopt
744
+ raise ArgumentError, "a short option name can't be a number or a dash" if sopt =~ ::Trollop::Parser::INVALID_SHORT_ARG_REGEX
745
+ end
746
+ return sopt
747
+ end
748
+
749
+ end
750
+
751
+ # Flag option. Has no arguments. Can be negated with "no-".
752
+ class BooleanOption < Option
753
+ register_alias :flag, :bool, :boolean, :trueclass, :falseclass
754
+ def initialize
755
+ super()
756
+ @default = false
757
+ end
758
+ def flag? ; true ; end
759
+ def parse(_paramlist, neg_given)
760
+ return(self.name.to_s =~ /^no_/ ? neg_given : !neg_given)
761
+ end
762
+ end
763
+
764
+ # Floating point number option class.
765
+ class FloatOption < Option
766
+ register_alias :float, :double
767
+ def type_format ; "=<f>" ; end
768
+ def parse(paramlist, _neg_given)
769
+ paramlist.map do |pg|
770
+ pg.map do |param|
771
+ raise CommandlineError, "option '#{self.name}' needs a floating-point number" unless param.is_a?(Numeric) || param =~ FLOAT_RE
772
+ param.to_f
773
+ end
774
+ end
775
+ end
776
+ end
777
+
778
+ # Integer number option class.
779
+ class IntegerOption < Option
780
+ register_alias :int, :integer, :fixnum
781
+ def type_format ; "=<i>" ; end
782
+ def parse(paramlist, _neg_given)
783
+ paramlist.map do |pg|
784
+ pg.map do |param|
785
+ raise CommandlineError, "option '#{self.name}' needs an integer" unless param.is_a?(Numeric) || param =~ /^-?[\d_]+$/
786
+ param.to_i
787
+ end
788
+ end
789
+ end
790
+ end
791
+
792
+ # Option class for handling IO objects and URLs.
793
+ # Note that this will return the file-handle, not the file-name
794
+ # in the case of file-paths given to it.
795
+ class IOOption < Option
796
+ register_alias :io
797
+ def type_format ; "=<filename/uri>" ; end
798
+ def parse(paramlist, _neg_given)
799
+ paramlist.map do |pg|
800
+ pg.map do |param|
801
+ if param =~ /^(stdin|-)$/i
802
+ $stdin
803
+ else
804
+ require 'open-uri'
805
+ begin
806
+ open param
807
+ rescue SystemCallError => e
808
+ raise CommandlineError, "file or url for option '#{self.name}' cannot be opened: #{e.message}"
809
+ end
810
+ end
811
+ end
812
+ end
813
+ end
814
+ end
815
+
816
+ # Option class for handling Strings.
817
+ class StringOption < Option
818
+ register_alias :string
819
+ def type_format ; "=<s>" ; end
820
+ def parse(paramlist, _neg_given)
821
+ paramlist.map { |pg| pg.map(&:to_s) }
822
+ end
823
+ end
824
+
825
+ # Option for dates. Uses Chronic if it exists.
826
+ class DateOption < Option
827
+ register_alias :date
828
+ def type_format ; "=<date>" ; end
829
+ def parse(paramlist, _neg_given)
830
+ paramlist.map do |pg|
831
+ pg.map do |param|
832
+ next param if param.is_a?(Date)
833
+ begin
834
+ begin
835
+ require 'chronic'
836
+ time = Chronic.parse(param)
837
+ rescue LoadError
838
+ # chronic is not available
839
+ end
840
+ time ? Date.new(time.year, time.month, time.day) : Date.parse(param)
841
+ rescue ArgumentError
842
+ raise CommandlineError, "option '#{self.name}' needs a date"
843
+ end
844
+ end
845
+ end
846
+ end
847
+ end
848
+
849
+ ### MULTI_OPT_TYPES :
850
+ ## The set of values that indicate a multiple-parameter option (i.e., that
851
+ ## takes multiple space-separated values on the commandline) when passed as
852
+ ## the +:type+ parameter of #opt.
853
+
854
+ # Option class for handling multiple Integers
855
+ class IntegerArrayOption < IntegerOption
856
+ register_alias :fixnums, :ints, :integers
857
+ def type_format ; "=<i+>" ; end
858
+ def multi_arg? ; true ; end
859
+ end
860
+
861
+ # Option class for handling multiple Floats
862
+ class FloatArrayOption < FloatOption
863
+ register_alias :doubles, :floats
864
+ def type_format ; "=<f+>" ; end
865
+ def multi_arg? ; true ; end
866
+ end
867
+
868
+ # Option class for handling multiple Strings
869
+ class StringArrayOption < StringOption
870
+ register_alias :strings
871
+ def type_format ; "=<s+>" ; end
872
+ def multi_arg? ; true ; end
873
+ end
874
+
875
+ # Option class for handling multiple dates
876
+ class DateArrayOption < DateOption
877
+ register_alias :dates
878
+ def type_format ; "=<date+>" ; end
879
+ def multi_arg? ; true ; end
880
+ end
881
+
882
+ # Option class for handling Files/URLs via 'open'
883
+ class IOArrayOption < IOOption
884
+ register_alias :ios
885
+ def type_format ; "=<filename/uri+>" ; end
886
+ def multi_arg? ; true ; end
887
+ end
888
+
712
889
  ## The easy, syntactic-sugary entry method into Trollop. Creates a Parser,
713
890
  ## passes the block to it, then parses +args+ with it, handling any errors or
714
891
  ## requests for help or version information appropriately (and then exiting).
@@ -740,7 +917,7 @@ end
740
917
  ## p opts # => {:monkey=>true, :name=>nil, :num_limbs=>4, :help=>false, :monkey_given=>true}
741
918
  ##
742
919
  ## See more examples at http://trollop.rubyforge.org.
743
- def options args=ARGV, *a, &b
920
+ def options(args = ARGV, *a, &b)
744
921
  @last_parser = Parser.new(*a, &b)
745
922
  with_standard_exception_handling(@last_parser) { @last_parser.parse args }
746
923
  end
@@ -770,20 +947,16 @@ end
770
947
  ##
771
948
  ## Requires passing in the parser object.
772
949
 
773
- def with_standard_exception_handling parser
774
- begin
775
- yield
776
- rescue CommandlineError => e
777
- $stderr.puts "Error: #{e.message}."
778
- $stderr.puts "Try --help for help."
779
- exit(-1)
780
- rescue HelpNeeded
781
- parser.educate
782
- exit
783
- rescue VersionNeeded
784
- puts parser.version
785
- exit
786
- end
950
+ def with_standard_exception_handling(parser)
951
+ yield
952
+ rescue CommandlineError => e
953
+ parser.die(e.message, nil, e.error_code)
954
+ rescue HelpNeeded
955
+ parser.educate
956
+ exit
957
+ rescue VersionNeeded
958
+ puts parser.version
959
+ exit
787
960
  end
788
961
 
789
962
  ## Informs the user that their usage of 'arg' was wrong, as detailed by
@@ -804,9 +977,13 @@ end
804
977
  ## end
805
978
  ##
806
979
  ## Trollop::die "need at least one filename" if ARGV.empty?
807
- def die arg, msg=nil
980
+ ##
981
+ ## An exit code can be provide if needed
982
+ ##
983
+ ## Trollop::die "need at least one filename", -2 if ARGV.empty?
984
+ def die(arg, msg = nil, error_code = nil)
808
985
  if @last_parser
809
- @last_parser.die arg, msg
986
+ @last_parser.die arg, msg, error_code
810
987
  else
811
988
  raise ArgumentError, "Trollop::die can only be called after Trollop::options"
812
989
  end
@@ -834,5 +1011,4 @@ def educate
834
1011
  end
835
1012
 
836
1013
  module_function :options, :die, :educate, :with_standard_exception_handling
837
-
838
1014
  end # module