trollop 2.1.2 → 2.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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