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 +4 -4
- data/.travis.yml +7 -3
- data/Gemfile.travis +3 -0
- data/History.txt +16 -2
- data/README.md +6 -3
- data/Rakefile +1 -1
- data/lib/trollop.rb +501 -325
- data/test/support/assert_helpers.rb +46 -0
- data/test/test_helper.rb +5 -1
- data/test/trollop/command_line_error_test.rb +27 -0
- data/test/trollop/help_needed_test.rb +19 -0
- data/test/trollop/parser_educate_test.rb +175 -0
- data/test/trollop/parser_opt_test.rb +14 -0
- data/test/trollop/parser_parse_test.rb +79 -0
- data/test/{test_trollop.rb → trollop/parser_test.rb} +155 -198
- data/test/trollop/version_needed_test.rb +19 -0
- data/test/trollop_test.rb +190 -0
- data/trollop.gemspec +7 -6
- metadata +27 -50
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: eceb56ea9f2bee171cbc8a7b88f4053de3024256
|
4
|
+
data.tar.gz: b275e0df3037f69de67475f9155b26e8e8a0cad4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f71c677c66f414b2e6fc4b1d41c815df416efd57feef1e5f1ada73ff1041025bf87ebb23cd679241ecb2aadea2dbe06976aa7ae3eeabee346de6884852b0bb38
|
7
|
+
data.tar.gz: d2ff306c1daa19c6f6026f7cd37e4f2165664878c638e1d9112da400503196be976b779d61edcee68688ab9efe8539a0e206c1bd6766b5caa0f0f6aeef21be0f
|
data/.travis.yml
CHANGED
data/Gemfile.travis
CHANGED
@@ -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'
|
data/History.txt
CHANGED
@@ -1,10 +1,20 @@
|
|
1
|
-
== 2.1.
|
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
|
-
|
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
|
[](https://coveralls.io/r/ManageIQ/trollop)
|
9
9
|
[](https://gemnasium.com/ManageIQ/trollop)
|
10
10
|
|
11
|
-
Documentation
|
12
|
-
|
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 © 2008-2014 [William Morgan](http://masanjin.net/).
|
|
54
57
|
|
55
58
|
Copyright © 2014 Red Hat, Inc.
|
56
59
|
|
57
|
-
Trollop is released under the [MIT License]
|
60
|
+
Trollop is released under the [MIT License](http://www.opensource.org/licenses/MIT).
|
data/Rakefile
CHANGED
data/lib/trollop.rb
CHANGED
@@ -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
|
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
|
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
|
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
|
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
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
##
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
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
|
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
|
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
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
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)
|
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)
|
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)
|
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
|
258
|
-
|
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
|
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
|
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
|
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
|
233
|
+
def parse(cmdline = ARGV)
|
300
234
|
vals = {}
|
301
235
|
required = {}
|
302
236
|
|
303
|
-
opt :version, "Print version and exit" if @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
|
308
|
-
vals[sym] = opts
|
309
|
-
vals[sym] = [] if opts
|
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 /^-([^-])
|
326
|
-
when /^--([^-]\S*)
|
327
|
-
else
|
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
|
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]
|
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.
|
348
|
-
if
|
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
|
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]
|
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]
|
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]
|
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
|
387
|
-
raise CommandlineError, "option '#{arg}' needs a parameter" unless opts
|
388
|
-
params << (opts
|
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
|
-
|
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
|
409
|
-
if opts
|
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
|
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
|
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, *
|
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
|
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
|
-
|
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}
|
481
|
-
stream.puts "
|
482
|
-
stream.puts if @usage
|
483
|
-
stream.puts "
|
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
|
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
|
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
|
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]
|
430
|
+
$stderr.puts "Error: argument --#{@specs[arg].long} #{msg}."
|
558
431
|
else
|
559
432
|
$stderr.puts "Error: #{arg}."
|
560
433
|
end
|
561
|
-
|
562
|
-
|
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
|
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)
|
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
|
-
|
585
|
-
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
|
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
|
-
|
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
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
|
609
|
-
|
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
|
-
|
491
|
+
else
|
492
|
+
i += num_params_taken
|
616
493
|
end
|
617
494
|
else
|
618
|
-
yield "-#{a}",
|
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
|
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
|
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
|
536
|
+
next if type != :opt || opts.short
|
671
537
|
|
672
|
-
c = opts
|
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
|
540
|
+
opts.short = c
|
675
541
|
@short[c] = name
|
676
542
|
end
|
677
543
|
end
|
678
544
|
end
|
679
545
|
|
680
|
-
def wrap_line
|
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
|
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
|
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
|
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
|
774
|
-
|
775
|
-
|
776
|
-
|
777
|
-
|
778
|
-
|
779
|
-
|
780
|
-
|
781
|
-
|
782
|
-
|
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
|
-
|
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
|