trollop 1.0
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.
- data/FAQ.txt +31 -0
- data/History.txt +2 -0
- data/Manifest.txt +7 -0
- data/README.txt +73 -0
- data/Rakefile +25 -0
- data/lib/trollop.rb +392 -0
- data/test/test_trollop.rb +300 -0
- metadata +59 -0
data/FAQ.txt
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
Trollop FAQ
|
2
|
+
-----------
|
3
|
+
|
4
|
+
Q: Whither Trollop?
|
5
|
+
A: There are more than a few pre-existing commandline argument
|
6
|
+
processing libraries for Ruby. I was never too happy with any of
|
7
|
+
them. They all seemed to want me to type way too much for what I
|
8
|
+
wanted to get done. I just wanted to parse some options and be
|
9
|
+
done with it.
|
10
|
+
|
11
|
+
Q: Whither "Trollop"?
|
12
|
+
A: No reason. Just thought it sounded funny.
|
13
|
+
|
14
|
+
Q: Why does Trollop disallow numeric short argument names, like '-1'
|
15
|
+
and '-9'?
|
16
|
+
A: Because it's ambiguous whether these are arguments or negative
|
17
|
+
integer or floating-point parameters to arguments. E.g., what
|
18
|
+
about "-f -3". Is that a negative three parameter to -f, or two
|
19
|
+
separate parameters?
|
20
|
+
|
21
|
+
I could be very clever and detect when there are no arguments
|
22
|
+
that require floating-point parameters, and allow such short option
|
23
|
+
names in those cases, but I'd rather be simple and consistent.
|
24
|
+
|
25
|
+
Q: Why does Trollop disallow options appearing multiple times, despite
|
26
|
+
the POSIX standard allowing it?
|
27
|
+
A: Because basically I think it's confusing, and more often than
|
28
|
+
not, a symptom of you making a mistake (e.g. getting lost in a long
|
29
|
+
command line and accidentally setting the same thing twice.) Given
|
30
|
+
that, I really don't see that much advantage to "-vvvvv" over "-v
|
31
|
+
5", so Trollop will produce an error if it happens.
|
data/History.txt
ADDED
data/Manifest.txt
ADDED
data/README.txt
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
== trollop
|
2
|
+
|
3
|
+
by William Morgan <wmorgan-trollop@masanjin.net>
|
4
|
+
|
5
|
+
http://trollop.rubyforge.org
|
6
|
+
|
7
|
+
== DESCRIPTION:
|
8
|
+
|
9
|
+
Trollop is YAFCLAP --- yet another fine commandline argument
|
10
|
+
processing library for Ruby. Trollop is designed to provide the
|
11
|
+
maximal amount of GNU-style argument processing in the minimum number
|
12
|
+
of lines of code (for you, the programmer).
|
13
|
+
|
14
|
+
Trollop provides a nice automatically-generated help page, robust
|
15
|
+
option parsing, and sensible defaults for everything you don't
|
16
|
+
specify.
|
17
|
+
|
18
|
+
Trollop: getting you 90% of the way there with only 10% of the effort.
|
19
|
+
|
20
|
+
== FEATURES/PROBLEMS:
|
21
|
+
|
22
|
+
- Simple usage.
|
23
|
+
- Sensible defaults. No tweaking necessary, much tweaking possible.
|
24
|
+
- Support for long options, short options, short option bundling,
|
25
|
+
and automatic type validation and conversion.
|
26
|
+
- Automatic help message generation, wrapped to current screen width.
|
27
|
+
- Lots of unit tests.
|
28
|
+
|
29
|
+
== SYNOPSIS:
|
30
|
+
|
31
|
+
###### simple ######
|
32
|
+
|
33
|
+
opts = Trollop::options do
|
34
|
+
opt :monkey, "Use monkey mode."
|
35
|
+
opt :goat, "Use goat model", :default => true
|
36
|
+
opt :num_limbs, "Set number of limbs", :default => 4
|
37
|
+
end
|
38
|
+
|
39
|
+
p opts
|
40
|
+
|
41
|
+
###### complex ######
|
42
|
+
|
43
|
+
opts = Trollop::options do
|
44
|
+
version "test 1.2.3 (c) 2007 William Morgan"
|
45
|
+
banner <<-EOS
|
46
|
+
Test is an awesome program that does something very, very important.
|
47
|
+
|
48
|
+
Usage:
|
49
|
+
test [options] <filenames>+
|
50
|
+
where [options] are:
|
51
|
+
EOS
|
52
|
+
|
53
|
+
opt :ignore, "Ignore incorrect values"
|
54
|
+
opt :file, "Extra data filename to read in, with a very long option description like this one", :type => String
|
55
|
+
opt :volume, "Volume level", :default => 3.0
|
56
|
+
opt :iters, "Number of iterations", :default => 5
|
57
|
+
end
|
58
|
+
Trollop::die :volume, "must be non-negative" if opts[:volume] < 0
|
59
|
+
Trollop::die :file, "must exist" unless File.exists?(opts[:file]) if opts[:file]
|
60
|
+
|
61
|
+
== REQUIREMENTS:
|
62
|
+
|
63
|
+
* none
|
64
|
+
|
65
|
+
== INSTALL:
|
66
|
+
|
67
|
+
* gem install trollop
|
68
|
+
|
69
|
+
== LICENSE:
|
70
|
+
|
71
|
+
Copyright (c) 2007 William Morgan.
|
72
|
+
|
73
|
+
Trollop is distributed under the same terms as Ruby.
|
data/Rakefile
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'hoe'
|
5
|
+
require 'trollop'
|
6
|
+
|
7
|
+
Hoe.new('trollop', Trollop::VERSION) do |p|
|
8
|
+
p.rubyforge_name = 'trollop'
|
9
|
+
p.author = "William Morgan"
|
10
|
+
p.summary = "Trollop is YAFCLAP --- yet another fine commandline argument processing library for Ruby. Trollop is designed to provide the maximal amount of GNU-style argument processing in the minimum number of lines of code (for you, the programmer)."
|
11
|
+
|
12
|
+
p.description = p.paragraphs_of('README.txt', 4..5, 9..18).join("\n\n").gsub(/== SYNOPSIS/, "Synopsis")
|
13
|
+
p.url = "http://trollop.rubyforge.org"
|
14
|
+
p.changes = p.paragraphs_of('History.txt', 0..0).join("\n\n")
|
15
|
+
p.email = "wmorgan-trollop@masanjin.net"
|
16
|
+
end
|
17
|
+
|
18
|
+
## is there really no way to make a rule for this?
|
19
|
+
WWW_FILES = %w(www/index.html README.txt FAQ.txt)
|
20
|
+
|
21
|
+
task :upload_webpage => WWW_FILES do |t|
|
22
|
+
sh "scp -C #{t.prerequisites * ' '} wmorgan@rubyforge.org:/var/www/gforge-projects/trollop/"
|
23
|
+
end
|
24
|
+
|
25
|
+
# vim: syntax=Ruby
|
data/lib/trollop.rb
ADDED
@@ -0,0 +1,392 @@
|
|
1
|
+
## lib/trollop.rb -- trollop command-line processing library
|
2
|
+
## Author:: William Morgan (mailto: wmorgan-trollop@masanjin.net)
|
3
|
+
## Copyright:: Copyright 2007 William Morgan
|
4
|
+
## License:: GNU GPL version 2
|
5
|
+
|
6
|
+
module Trollop
|
7
|
+
|
8
|
+
VERSION = "1.0"
|
9
|
+
|
10
|
+
## Thrown by Parser in the event of a commandline error. Not needed if
|
11
|
+
## you're using the Trollop::options entry.
|
12
|
+
class CommandlineError < StandardError; end
|
13
|
+
|
14
|
+
## Thrown by Parser if the user passes in '-h' or '--help'. Handled
|
15
|
+
## automatically by Trollop#options.
|
16
|
+
class HelpNeeded < StandardError; end
|
17
|
+
|
18
|
+
## Thrown by Parser if the user passes in '-h' or '--version'. Handled
|
19
|
+
## automatically by Trollop#options.
|
20
|
+
class VersionNeeded < StandardError; end
|
21
|
+
|
22
|
+
## Regex for floating point numbers
|
23
|
+
FLOAT_RE = /^-?((\d+(\.\d+)?)|(\.\d+))$/
|
24
|
+
|
25
|
+
## Regex for parameters
|
26
|
+
PARAM_RE = /^-(-|\.$|[^\d\.])/
|
27
|
+
|
28
|
+
## The commandline parser. In typical usage, the methods in this class
|
29
|
+
## will be handled internally by Trollop#options, in which case only
|
30
|
+
## the methods #opt, #banner and #version will be called.
|
31
|
+
class Parser
|
32
|
+
## The set of values specifiable as the :type parameter to #opt.
|
33
|
+
TYPES = [:flag, :boolean, :bool, :int, :integer, :string, :double, :float]
|
34
|
+
|
35
|
+
## The values from the commandline that were not interpreted by #parse.
|
36
|
+
attr_reader :leftovers
|
37
|
+
|
38
|
+
## The complete configuration hashes for each option. (Mainly useful
|
39
|
+
## for testing.)
|
40
|
+
attr_reader :specs
|
41
|
+
|
42
|
+
## Initializes the parser, and instance-evaluates any block given.
|
43
|
+
def initialize &b
|
44
|
+
@version = nil
|
45
|
+
@banner = nil
|
46
|
+
@leftovers = []
|
47
|
+
@specs = {}
|
48
|
+
@long = {}
|
49
|
+
@short = {}
|
50
|
+
|
51
|
+
opt :help, "Show this message"
|
52
|
+
instance_eval(&b) if b
|
53
|
+
end
|
54
|
+
|
55
|
+
## Add an option. 'name' is the argument name, a unique identifier
|
56
|
+
## for the option that you will use internally. 'desc' a string
|
57
|
+
## description which will be displayed in help messages. Takes the
|
58
|
+
## following optional arguments:
|
59
|
+
##
|
60
|
+
## * :long: Specify the long form of the argument, i.e. the form
|
61
|
+
## with two dashes. If unspecified, will be automatically derived
|
62
|
+
## based on the argument name.
|
63
|
+
## * :short: Specify the short form of the argument, i.e. the form
|
64
|
+
## with one dash. If unspecified, will be automatically derived
|
65
|
+
## based on the argument name.
|
66
|
+
## * :type: Require that the argument take a parameter of type
|
67
|
+
## 'type'. Can by any member of the TYPES constant or a
|
68
|
+
## corresponding class (e.g. Integer for :int). If unset, the
|
69
|
+
## default argument type is :flag, meaning the argument does not
|
70
|
+
## take a parameter. Not necessary if :default: is specified.
|
71
|
+
## * :default: Set the default value for an argument. Without a
|
72
|
+
## default value, the hash returned by #parse (and thus
|
73
|
+
## Trollop#options) will not contain the argument unless it is
|
74
|
+
## given on the commandline. The argument type is derived
|
75
|
+
## automatically from the class of the default value given, if
|
76
|
+
## any. Specifying a :flag argument on the commandline whose
|
77
|
+
## default value is true will change its value to false.
|
78
|
+
## * :required: if set to true, the argument must be provided on the
|
79
|
+
## commandline.
|
80
|
+
def opt name, desc="", opts={}
|
81
|
+
raise ArgumentError, "you already have an argument named #{name.inspect}" if @specs.member? name
|
82
|
+
|
83
|
+
## fill in :type
|
84
|
+
opts[:type] =
|
85
|
+
case opts[:type]
|
86
|
+
when :flag, :boolean, :bool: :flag
|
87
|
+
when :int, :integer: :int
|
88
|
+
when :string: :string
|
89
|
+
when :double, :float: :float
|
90
|
+
when Class
|
91
|
+
case opts[:type].to_s # sigh... there must be a better way to do this
|
92
|
+
when 'TrueClass', 'FalseClass': :flag
|
93
|
+
when 'String': :string
|
94
|
+
when 'Integer': :int
|
95
|
+
when 'Float': :float
|
96
|
+
else
|
97
|
+
raise ArgumentError, "unsupported argument type '#{opts[:type].class.name}'"
|
98
|
+
end
|
99
|
+
when nil: nil
|
100
|
+
else
|
101
|
+
raise ArgumentError, "unsupported argument type '#{opts[:type]}'" unless TYPES.include?(opts[:type])
|
102
|
+
end
|
103
|
+
|
104
|
+
type_from_default =
|
105
|
+
case opts[:default]
|
106
|
+
when Integer: :int
|
107
|
+
when Numeric: :float
|
108
|
+
when TrueClass, FalseClass: :flag
|
109
|
+
when String: :string
|
110
|
+
when nil: nil
|
111
|
+
else
|
112
|
+
raise ArgumentError, "unsupported argument type '#{opts[:default].class.name}'"
|
113
|
+
end
|
114
|
+
|
115
|
+
raise ArgumentError, ":type specification and default type don't match" if opts[:type] && type_from_default && opts[:type] != type_from_default
|
116
|
+
|
117
|
+
opts[:type] = (opts[:type] || type_from_default || :flag)
|
118
|
+
|
119
|
+
## fill in :long
|
120
|
+
opts[:long] = opts[:long] ? opts[:long].to_s : name.to_s.gsub("_", "-")
|
121
|
+
opts[:long] =
|
122
|
+
case opts[:long]
|
123
|
+
when /^--([^-].*)$/
|
124
|
+
$1
|
125
|
+
when /^[^-]/
|
126
|
+
opts[:long]
|
127
|
+
else
|
128
|
+
raise ArgumentError, "invalid long option name #{opts[:long].inspect}"
|
129
|
+
end
|
130
|
+
raise ArgumentError, "long option name #{opts[:long].inspect} is already taken; please specify a (different) :long" if @long[opts[:long]]
|
131
|
+
|
132
|
+
## fill in :short
|
133
|
+
opts[:short] = opts[:short].to_s if opts[:short]
|
134
|
+
opts[:short] =
|
135
|
+
case opts[:short]
|
136
|
+
when nil
|
137
|
+
opts[:long].split(//).find { |c| c !~ /[\d]/ && !@short.member?(c) }
|
138
|
+
when /^-(.)$/
|
139
|
+
$1
|
140
|
+
when /^.$/
|
141
|
+
opts[:short]
|
142
|
+
else
|
143
|
+
raise ArgumentError, "invalid short option name #{opts[:long].inspect}"
|
144
|
+
end
|
145
|
+
raise ArgumentError, "can't generate a short option name (out of characters)" unless opts[:short]
|
146
|
+
raise ArgumentError, "short option name #{opts[:short].inspect} is already taken; please specify a (different) :short" if @short[opts[:short]]
|
147
|
+
raise ArgumentError, "a short option name can't be a number or a dash" if opts[:short] =~ /[\d-]/
|
148
|
+
|
149
|
+
## fill in :default for flags
|
150
|
+
opts[:default] = false if opts[:type] == :flag && opts[:default].nil?
|
151
|
+
|
152
|
+
opts[:desc] ||= desc
|
153
|
+
@short[opts[:short]] = @long[opts[:long]] = name
|
154
|
+
@specs[name] = opts
|
155
|
+
end
|
156
|
+
|
157
|
+
## Sets the version string. If set, the user can request the version
|
158
|
+
## on the commandline. Should be of the form "<program name>
|
159
|
+
## <version number>".
|
160
|
+
def version s=nil;
|
161
|
+
if s
|
162
|
+
@version = s
|
163
|
+
opt :version, "Print version and exit"
|
164
|
+
end
|
165
|
+
@version
|
166
|
+
end
|
167
|
+
|
168
|
+
## Sets the banner. If set, this will be printed at the top of the
|
169
|
+
## help display.
|
170
|
+
def banner s=nil; @banner = s if s; @banner end
|
171
|
+
|
172
|
+
## yield successive arg, parameter pairs
|
173
|
+
def each_arg args # :nodoc:
|
174
|
+
remains = []
|
175
|
+
i = 0
|
176
|
+
|
177
|
+
until i >= args.length
|
178
|
+
case args[i]
|
179
|
+
when /^--$/ # arg terminator
|
180
|
+
remains += args[(i + 1) .. -1]
|
181
|
+
break
|
182
|
+
when /^--(\S+?)=(\S+)$/ # long argument with equals
|
183
|
+
yield "--#{$1}", $2
|
184
|
+
i += 1
|
185
|
+
when /^--(\S+)$/ # long argument
|
186
|
+
if args[i + 1] && args[i + 1] !~ PARAM_RE
|
187
|
+
remains << args[i + 1] unless yield args[i], args[i + 1]
|
188
|
+
i += 2
|
189
|
+
else # long argument no parameter
|
190
|
+
yield args[i], nil
|
191
|
+
i += 1
|
192
|
+
end
|
193
|
+
when /^-(\S+)$/ # one or more short arguments
|
194
|
+
shortargs = $1.split(//)
|
195
|
+
shortargs.each_with_index do |a, j|
|
196
|
+
if j == (shortargs.length - 1) && args[i + 1] && args[i + 1] !~ PARAM_RE
|
197
|
+
remains << args[i + 1] unless yield "-#{a}", args[i + 1]
|
198
|
+
i += 1 # once more below
|
199
|
+
else
|
200
|
+
yield "-#{a}", nil
|
201
|
+
end
|
202
|
+
end
|
203
|
+
i += 1
|
204
|
+
else
|
205
|
+
remains << args[i]
|
206
|
+
i += 1
|
207
|
+
end
|
208
|
+
end
|
209
|
+
remains
|
210
|
+
end
|
211
|
+
|
212
|
+
def parse args #:nodoc:
|
213
|
+
vals = {}
|
214
|
+
required = {}
|
215
|
+
found = {}
|
216
|
+
|
217
|
+
@specs.each do |name, opts|
|
218
|
+
required[name] = true if opts[:required]
|
219
|
+
vals[name] = opts[:default]
|
220
|
+
end
|
221
|
+
|
222
|
+
@leftovers = each_arg args do |arg, param|
|
223
|
+
raise VersionNeeded if @version && %w(-v --version).include?(arg)
|
224
|
+
raise HelpNeeded if %w(-h --help).include?(arg)
|
225
|
+
|
226
|
+
name =
|
227
|
+
case arg
|
228
|
+
when /^-([^-])$/
|
229
|
+
@short[$1]
|
230
|
+
when /^--([^-]\S*)$/
|
231
|
+
@long[$1]
|
232
|
+
else
|
233
|
+
raise CommandlineError, "invalid argument syntax: '#{arg}'"
|
234
|
+
end
|
235
|
+
raise CommandlineError, "unknown argument '#{arg}'" unless name
|
236
|
+
raise CommandlineError, "option '#{arg}' specified multiple times" if found[name]
|
237
|
+
|
238
|
+
found[name] = true
|
239
|
+
opts = @specs[name]
|
240
|
+
|
241
|
+
case opts[:type]
|
242
|
+
when :flag
|
243
|
+
vals[name] = !opts[:default]
|
244
|
+
false
|
245
|
+
when :int
|
246
|
+
raise CommandlineError, "option '#{arg}' needs a parameter" unless param
|
247
|
+
raise CommandlineError, "option '#{arg}' needs an integer" unless param =~ /^\d+$/
|
248
|
+
vals[name] = param.to_i
|
249
|
+
true
|
250
|
+
when :float
|
251
|
+
raise CommandlineError, "option '#{arg}' needs a parameter" unless param
|
252
|
+
raise CommandlineError, "option '#{arg}' needs a floating-point number" unless param =~ FLOAT_RE
|
253
|
+
vals[name] = param.to_f
|
254
|
+
true
|
255
|
+
when :string
|
256
|
+
raise CommandlineError, "option '#{arg}' needs a parameter" unless param
|
257
|
+
vals[name] = param
|
258
|
+
true
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
raise CommandlineError, "option '#{required.keys.first}' must be specified" if required.any? { |name, x| !found[name] }
|
263
|
+
vals
|
264
|
+
end
|
265
|
+
|
266
|
+
def width #:nodoc:
|
267
|
+
@width ||=
|
268
|
+
begin
|
269
|
+
require 'curses'
|
270
|
+
Curses::init_screen
|
271
|
+
x = Curses::cols
|
272
|
+
Curses::close_screen
|
273
|
+
x
|
274
|
+
rescue Exception
|
275
|
+
80
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
## Print the help message to 'stream'.
|
280
|
+
def educate stream=$stdout
|
281
|
+
width # just calculate it now; otherwise we have to be careful not to
|
282
|
+
# call this unless the cursor's at the beginning of a line.
|
283
|
+
if @banner
|
284
|
+
stream.puts wrap(@banner)
|
285
|
+
elsif @version
|
286
|
+
stream.puts
|
287
|
+
stream.puts @version
|
288
|
+
end
|
289
|
+
|
290
|
+
unless @banner
|
291
|
+
stream.puts "Options: "
|
292
|
+
end
|
293
|
+
|
294
|
+
specs = @long.keys.sort.map { |longname| @specs[@long[longname]] }
|
295
|
+
leftcols = specs.map { |spec| "--#{spec[:long]}, -#{spec[:short]}" }
|
296
|
+
leftcol_width = leftcols.map { |s| s.length }.max
|
297
|
+
rightcol_start = leftcol_width + 6 # spaces
|
298
|
+
specs.each_with_index do |spec, i|
|
299
|
+
stream.printf(" %#{leftcol_width}s: ", leftcols[i]);
|
300
|
+
desc = spec[:desc] +
|
301
|
+
if spec[:default]
|
302
|
+
if spec[:desc] =~ /\.$/
|
303
|
+
" (Default: #{spec[:default]})"
|
304
|
+
else
|
305
|
+
" (default: #{spec[:default]})"
|
306
|
+
end
|
307
|
+
else
|
308
|
+
""
|
309
|
+
end
|
310
|
+
stream.puts wrap(desc, :width => width - rightcol_start, :prefix => rightcol_start)
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
def wrap_line str, opts={} # :nodoc:
|
315
|
+
prefix = opts[:prefix] || 0
|
316
|
+
width = opts[:width] || self.width
|
317
|
+
start = 0
|
318
|
+
ret = []
|
319
|
+
until start > str.length
|
320
|
+
nextt =
|
321
|
+
if start + width >= str.length
|
322
|
+
str.length
|
323
|
+
else
|
324
|
+
x = str.rindex(/\s/, start + width)
|
325
|
+
x = str.index(/\s/, start) if x && x < start
|
326
|
+
x || str.length
|
327
|
+
end
|
328
|
+
ret << (ret.empty? ? "" : " " * prefix) + str[start ... nextt]
|
329
|
+
start = nextt + 1
|
330
|
+
end
|
331
|
+
ret
|
332
|
+
end
|
333
|
+
|
334
|
+
def wrap str, opts={} # :nodoc:
|
335
|
+
if str == ""
|
336
|
+
[""]
|
337
|
+
else
|
338
|
+
str.split("\n").map { |s| wrap_line s, opts }.flatten
|
339
|
+
end
|
340
|
+
end
|
341
|
+
end
|
342
|
+
|
343
|
+
## The top-level entry method into Trollop. Creates a Parser object,
|
344
|
+
## passes the block to it, then parses ARGV with it, handling any
|
345
|
+
## errors or requests for help or version information appropriately
|
346
|
+
## (and then exiting). Modifies ARGV in place. Returns a hash of
|
347
|
+
## option values.
|
348
|
+
##
|
349
|
+
## The block passed in should contain one or more calls to #opt
|
350
|
+
## (Parser#opt), and optionally a call to banner (Parser#banner)
|
351
|
+
## and a call to version (Parser#version).
|
352
|
+
##
|
353
|
+
## See the synopsis in README.txt for examples.
|
354
|
+
def options &b
|
355
|
+
@p = Parser.new(&b)
|
356
|
+
begin
|
357
|
+
vals = @p.parse ARGV
|
358
|
+
ARGV.clear
|
359
|
+
@p.leftovers.each { |l| ARGV << l }
|
360
|
+
vals
|
361
|
+
rescue CommandlineError => e
|
362
|
+
$stderr.puts "Error: #{e.message}."
|
363
|
+
$stderr.puts "Try --help for help."
|
364
|
+
exit(-1)
|
365
|
+
rescue HelpNeeded
|
366
|
+
@p.educate
|
367
|
+
exit
|
368
|
+
rescue VersionNeeded
|
369
|
+
puts @p.version
|
370
|
+
exit
|
371
|
+
end
|
372
|
+
end
|
373
|
+
|
374
|
+
## Informs the user that their usage of 'arg' was wrong, as detailed by
|
375
|
+
## 'msg', and dies. Example:
|
376
|
+
##
|
377
|
+
## options do
|
378
|
+
## opt :volume, :default => 0.0
|
379
|
+
## end
|
380
|
+
##
|
381
|
+
## die :volume, "too loud" if opts[:volume] > 10.0
|
382
|
+
## die :volume, "too soft" if opts[:volume] < 0.1
|
383
|
+
|
384
|
+
def die arg, msg
|
385
|
+
$stderr.puts "Error: parameter for option '--#{@p.specs[arg][:long]}' or '-#{@p.specs[arg][:short]}' #{msg}."
|
386
|
+
$stderr.puts "Try --help for help."
|
387
|
+
exit(-1)
|
388
|
+
end
|
389
|
+
|
390
|
+
module_function :options, :die
|
391
|
+
|
392
|
+
end # module
|
@@ -0,0 +1,300 @@
|
|
1
|
+
## test/test_trollop.rb -- unit tests for trollop
|
2
|
+
## Author:: William Morgan (mailto: wmorgan-trollop@masanjin.net)
|
3
|
+
## Copyright:: Copyright 2007 William Morgan
|
4
|
+
## License:: GNU GPL version 2
|
5
|
+
|
6
|
+
require 'test/unit'
|
7
|
+
require 'yaml'
|
8
|
+
require 'trollop'
|
9
|
+
|
10
|
+
module Trollop
|
11
|
+
module Test
|
12
|
+
|
13
|
+
class Trollop < ::Test::Unit::TestCase
|
14
|
+
def setup
|
15
|
+
@p = Parser.new
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_unknown_arguments
|
19
|
+
assert_raise(CommandlineError) { @p.parse(%w(--arg)) }
|
20
|
+
@p.opt "arg", "desc"
|
21
|
+
assert_nothing_raised { @p.parse(%w(--arg)) }
|
22
|
+
assert_raise(CommandlineError) { @p.parse(%w(--arg2)) }
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_syntax_check
|
26
|
+
@p.opt "arg", "desc"
|
27
|
+
|
28
|
+
assert_nothing_raised { @p.parse(%w(--arg)) }
|
29
|
+
assert_nothing_raised { @p.parse(%w(arg)) }
|
30
|
+
assert_raise(CommandlineError) { @p.parse(%w(---arg)) }
|
31
|
+
assert_raise(CommandlineError) { @p.parse(%w(-arg)) }
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_required_flags_are_required
|
35
|
+
@p.opt "arg", "desc", :required => true
|
36
|
+
@p.opt "arg2", "desc", :required => false
|
37
|
+
@p.opt "arg3", "desc", :required => false
|
38
|
+
|
39
|
+
assert_nothing_raised { @p.parse(%w(--arg)) }
|
40
|
+
assert_nothing_raised { @p.parse(%w(--arg --arg2)) }
|
41
|
+
assert_raise(CommandlineError) { @p.parse(%w(--arg2)) }
|
42
|
+
assert_raise(CommandlineError) { @p.parse(%w(--arg2 --arg3)) }
|
43
|
+
end
|
44
|
+
|
45
|
+
## flags that take an argument error unless given one
|
46
|
+
def test_argflags_demand_args
|
47
|
+
@p.opt "goodarg", "desc", :type => String
|
48
|
+
@p.opt "goodarg2", "desc", :type => String
|
49
|
+
|
50
|
+
assert_nothing_raised { @p.parse(%w(--goodarg goat)) }
|
51
|
+
assert_raise(CommandlineError) { @p.parse(%w(--goodarg --goodarg2 goat)) }
|
52
|
+
assert_raise(CommandlineError) { @p.parse(%w(--goodarg)) }
|
53
|
+
end
|
54
|
+
|
55
|
+
## flags that don't take arguments ignore them
|
56
|
+
def test_arglessflags_refuse_args
|
57
|
+
@p.opt "goodarg", "desc"
|
58
|
+
@p.opt "goodarg2", "desc"
|
59
|
+
assert_nothing_raised { @p.parse(%w(--goodarg)) }
|
60
|
+
assert_nothing_raised { @p.parse(%w(--goodarg --goodarg2)) }
|
61
|
+
opts = @p.parse %w(--goodarg a)
|
62
|
+
assert_equal true, opts["goodarg"]
|
63
|
+
assert_equal ["a"], @p.leftovers
|
64
|
+
end
|
65
|
+
|
66
|
+
## flags that require args of a specific type refuse args of other
|
67
|
+
## types
|
68
|
+
def test_typed_args_refuse_args_of_other_types
|
69
|
+
assert_nothing_raised { @p.opt "goodarg", "desc", :type => :int }
|
70
|
+
assert_raise(ArgumentError) { @p.opt "badarg", "desc", :type => :asdf }
|
71
|
+
|
72
|
+
assert_nothing_raised { @p.parse(%w(--goodarg 3)) }
|
73
|
+
assert_raise(CommandlineError) { @p.parse(%w(--goodarg 4.2)) }
|
74
|
+
assert_raise(CommandlineError) { @p.parse(%w(--goodarg hello)) }
|
75
|
+
end
|
76
|
+
|
77
|
+
## type is correctly derived from :default
|
78
|
+
def test_type_correctly_derived_from_default
|
79
|
+
assert_nothing_raised { @p.opt "goodarg", "desc", :default => 0 }
|
80
|
+
assert_raise(ArgumentError) { @p.opt "badarg", "desc", :default => [] }
|
81
|
+
|
82
|
+
assert_nothing_raised { @p.parse(%w(--goodarg 3)) }
|
83
|
+
assert_raise(CommandlineError) { @p.parse(%w(--goodarg 4.2)) }
|
84
|
+
assert_raise(CommandlineError) { @p.parse(%w(--goodarg hello)) }
|
85
|
+
end
|
86
|
+
|
87
|
+
## :type and :default must match if both are specified
|
88
|
+
def test_type_and_default_must_match
|
89
|
+
assert_nothing_raised { @p.opt "goodarg", "desc", :type => :int, :default => 4 }
|
90
|
+
assert_nothing_raised { @p.opt "goodarg2", "desc", :type => :string, :default => "yo" }
|
91
|
+
assert_raise(ArgumentError) { @p.opt "badarg", "desc", :type => :int, :default => "hello" }
|
92
|
+
assert_raise(ArgumentError) { @p.opt "badarg2", "desc", :type => :String, :default => 4 }
|
93
|
+
end
|
94
|
+
|
95
|
+
def test_long_detects_bad_names
|
96
|
+
assert_nothing_raised { @p.opt "goodarg", "desc", :long => "none" }
|
97
|
+
assert_nothing_raised { @p.opt "goodarg2", "desc", :long => "--two" }
|
98
|
+
assert_raise(ArgumentError) { @p.opt "badarg", "desc", :long => "" }
|
99
|
+
assert_raise(ArgumentError) { @p.opt "badarg2", "desc", :long => "--" }
|
100
|
+
assert_raise(ArgumentError) { @p.opt "badarg3", "desc", :long => "-one" }
|
101
|
+
assert_raise(ArgumentError) { @p.opt "badarg4", "desc", :long => "---toomany" }
|
102
|
+
end
|
103
|
+
|
104
|
+
def test_short_detects_bad_names
|
105
|
+
assert_nothing_raised { @p.opt "goodarg", "desc", :short => "a" }
|
106
|
+
assert_nothing_raised { @p.opt "goodarg2", "desc", :short => "-b" }
|
107
|
+
assert_raise(ArgumentError) { @p.opt "badarg", "desc", :short => "" }
|
108
|
+
assert_raise(ArgumentError) { @p.opt "badarg2", "desc", :short => "-ab" }
|
109
|
+
assert_raise(ArgumentError) { @p.opt "badarg3", "desc", :short => "--t" }
|
110
|
+
end
|
111
|
+
|
112
|
+
def test_short_names_determined_automatically
|
113
|
+
@p.opt "arg"
|
114
|
+
@p.opt "arg2"
|
115
|
+
@p.opt "arg3"
|
116
|
+
assert_raise(ArgumentError) { @p.opt "gra" }
|
117
|
+
opts = @p.parse %w(-a -g)
|
118
|
+
assert_equal true, opts["arg"]
|
119
|
+
assert_equal false, opts["arg2"]
|
120
|
+
assert_equal true, opts["arg3"]
|
121
|
+
end
|
122
|
+
|
123
|
+
## two args can't have the same name
|
124
|
+
def test_conflicting_names_are_detected
|
125
|
+
assert_nothing_raised { @p.opt "goodarg", "desc" }
|
126
|
+
assert_raise(ArgumentError) { @p.opt "goodarg", "desc" }
|
127
|
+
end
|
128
|
+
|
129
|
+
## two args can't have the same :long
|
130
|
+
def test_conflicting_longs_detected
|
131
|
+
assert_nothing_raised { @p.opt "goodarg", "desc", :long => "--goodarg" }
|
132
|
+
assert_raise(ArgumentError) { @p.opt "badarg", "desc", :long => "--goodarg" }
|
133
|
+
end
|
134
|
+
|
135
|
+
## two args can't have the same :short
|
136
|
+
def test_conflicting_shorts_detected
|
137
|
+
assert_nothing_raised { @p.opt "goodarg", "desc", :short => "-g" }
|
138
|
+
assert_raise(ArgumentError) { @p.opt "badarg", "desc", :short => "-g" }
|
139
|
+
end
|
140
|
+
|
141
|
+
def test_flag_defaults
|
142
|
+
@p.opt "defaultfalse", "desc"
|
143
|
+
@p.opt "defaulttrue", "desc", :default => true
|
144
|
+
opts = @p.parse []
|
145
|
+
assert_equal false, opts["defaultfalse"]
|
146
|
+
assert_equal true, opts["defaulttrue"]
|
147
|
+
|
148
|
+
opts = @p.parse %w(--defaultfalse --defaulttrue)
|
149
|
+
assert_equal true, opts["defaultfalse"]
|
150
|
+
assert_equal false, opts["defaulttrue"]
|
151
|
+
end
|
152
|
+
|
153
|
+
def test_special_flags_work
|
154
|
+
@p.version "asdf fdas"
|
155
|
+
assert_raise(VersionNeeded) { @p.parse(%w(-v)) }
|
156
|
+
assert_raise(HelpNeeded) { @p.parse(%w(-h)) }
|
157
|
+
end
|
158
|
+
|
159
|
+
def test_short_options_combine
|
160
|
+
@p.opt :arg1, "desc", :short => "a"
|
161
|
+
@p.opt :arg2, "desc", :short => "b"
|
162
|
+
@p.opt :arg3, "desc", :short => "c", :type => :int
|
163
|
+
|
164
|
+
opts = nil
|
165
|
+
assert_nothing_raised { opts = @p.parse %w(-a -b) }
|
166
|
+
assert_equal true, opts[:arg1]
|
167
|
+
assert_equal true, opts[:arg2]
|
168
|
+
assert_equal nil, opts[:arg3]
|
169
|
+
|
170
|
+
assert_nothing_raised { opts = @p.parse %w(-ab) }
|
171
|
+
assert_equal true, opts[:arg1]
|
172
|
+
assert_equal true, opts[:arg2]
|
173
|
+
assert_equal nil, opts[:arg3]
|
174
|
+
|
175
|
+
assert_nothing_raised { opts = @p.parse %w(-ac 4 -b) }
|
176
|
+
assert_equal true, opts[:arg1]
|
177
|
+
assert_equal true, opts[:arg2]
|
178
|
+
assert_equal 4, opts[:arg3]
|
179
|
+
|
180
|
+
assert_raises(CommandlineError) { @p.parse %w(-cab 4) }
|
181
|
+
assert_raises(CommandlineError) { @p.parse %w(-cba 4) }
|
182
|
+
end
|
183
|
+
|
184
|
+
def test_version_only_appears_if_set
|
185
|
+
@p.opt "arg", "desc"
|
186
|
+
assert_raise(CommandlineError) { @p.parse %w(-v) }
|
187
|
+
@p.version "trollop 1.2.3.4"
|
188
|
+
assert_raise(VersionNeeded) { @p.parse %w(-v) }
|
189
|
+
end
|
190
|
+
|
191
|
+
def test_doubledash_ends_option_processing
|
192
|
+
@p.opt :arg1, "desc", :short => "a", :default => 0
|
193
|
+
@p.opt :arg2, "desc", :short => "b", :default => 0
|
194
|
+
opts = nil
|
195
|
+
assert_nothing_raised { opts = @p.parse %w(-- -a 3 -b 2) }
|
196
|
+
assert_equal opts[:arg1], 0
|
197
|
+
assert_equal opts[:arg2], 0
|
198
|
+
assert_equal %w(-a 3 -b 2), @p.leftovers
|
199
|
+
assert_nothing_raised { opts = @p.parse %w(-a 3 -- -b 2) }
|
200
|
+
assert_equal opts[:arg1], 3
|
201
|
+
assert_equal opts[:arg2], 0
|
202
|
+
assert_equal %w(-b 2), @p.leftovers
|
203
|
+
assert_nothing_raised { opts = @p.parse %w(-a 3 -b 2 --) }
|
204
|
+
assert_equal opts[:arg1], 3
|
205
|
+
assert_equal opts[:arg2], 2
|
206
|
+
assert_equal %w(), @p.leftovers
|
207
|
+
end
|
208
|
+
|
209
|
+
def test_wrap
|
210
|
+
assert_equal [""], @p.wrap("")
|
211
|
+
assert_equal ["a"], @p.wrap("a")
|
212
|
+
assert_equal ["one two", "three"], @p.wrap("one two three", :width => 8)
|
213
|
+
assert_equal ["one two three"], @p.wrap("one two three", :width => 80)
|
214
|
+
assert_equal ["one", "two", "three"], @p.wrap("one two three", :width => 3)
|
215
|
+
assert_equal ["onetwothree"], @p.wrap("onetwothree", :width => 3)
|
216
|
+
assert_equal [
|
217
|
+
"Test is an awesome program that does something very, very important.",
|
218
|
+
"",
|
219
|
+
"Usage:",
|
220
|
+
" test [options] <filenames>+",
|
221
|
+
"where [options] are:"], @p.wrap(<<EOM, :width => 100)
|
222
|
+
Test is an awesome program that does something very, very important.
|
223
|
+
|
224
|
+
Usage:
|
225
|
+
test [options] <filenames>+
|
226
|
+
where [options] are:
|
227
|
+
EOM
|
228
|
+
end
|
229
|
+
|
230
|
+
def test_floating_point_formatting
|
231
|
+
@p.opt :arg, "desc", :type => :float, :short => "f"
|
232
|
+
opts = nil
|
233
|
+
assert_nothing_raised { opts = @p.parse %w(-f 1) }
|
234
|
+
assert_equal 1.0, opts[:arg]
|
235
|
+
assert_nothing_raised { opts = @p.parse %w(-f 1.0) }
|
236
|
+
assert_equal 1.0, opts[:arg]
|
237
|
+
assert_nothing_raised { opts = @p.parse %w(-f 0.1) }
|
238
|
+
assert_equal 0.1, opts[:arg]
|
239
|
+
assert_nothing_raised { opts = @p.parse %w(-f .1) }
|
240
|
+
assert_equal 0.1, opts[:arg]
|
241
|
+
assert_nothing_raised { opts = @p.parse %w(-f .99999999999999999999) }
|
242
|
+
assert_equal 1.0, opts[:arg]
|
243
|
+
assert_nothing_raised { opts = @p.parse %w(-f -1) }
|
244
|
+
assert_equal(-1.0, opts[:arg])
|
245
|
+
assert_nothing_raised { opts = @p.parse %w(-f -1.0) }
|
246
|
+
assert_equal(-1.0, opts[:arg])
|
247
|
+
assert_nothing_raised { opts = @p.parse %w(-f -0.1) }
|
248
|
+
assert_equal(-0.1, opts[:arg])
|
249
|
+
assert_nothing_raised { opts = @p.parse %w(-f -.1) }
|
250
|
+
assert_equal(-0.1, opts[:arg])
|
251
|
+
assert_raises(CommandlineError) { @p.parse %w(-f a) }
|
252
|
+
assert_raises(CommandlineError) { @p.parse %w(-f 1a) }
|
253
|
+
assert_raises(CommandlineError) { @p.parse %w(-f 1.a) }
|
254
|
+
assert_raises(CommandlineError) { @p.parse %w(-f a.1) }
|
255
|
+
assert_raises(CommandlineError) { @p.parse %w(-f 1.0.0) }
|
256
|
+
assert_raises(CommandlineError) { @p.parse %w(-f .) }
|
257
|
+
assert_raises(CommandlineError) { @p.parse %w(-f -.) }
|
258
|
+
end
|
259
|
+
|
260
|
+
def test_short_options_cant_be_numeric
|
261
|
+
assert_raises(ArgumentError) { @p.opt :arg, "desc", :short => "-1" }
|
262
|
+
@p.opt :a1b, "desc"
|
263
|
+
@p.opt :a2b, "desc"
|
264
|
+
assert_not_equal "2", @p.specs[:a2b][:short]
|
265
|
+
end
|
266
|
+
|
267
|
+
def test_short_options_can_be_weird
|
268
|
+
assert_nothing_raised { @p.opt :arg1, "desc", :short => "#" }
|
269
|
+
assert_nothing_raised { @p.opt :arg2, "desc", :short => "." }
|
270
|
+
assert_raises(ArgumentError) { @p.opt :arg3, "desc", :short => "-" }
|
271
|
+
end
|
272
|
+
|
273
|
+
def test_options_cant_be_set_multiple_times
|
274
|
+
@p.opt :arg, "desc", :short => "-x"
|
275
|
+
assert_nothing_raised { @p.parse %w(-x) }
|
276
|
+
assert_raises(CommandlineError) { @p.parse %w(-x -x) }
|
277
|
+
assert_raises(CommandlineError) { @p.parse %w(-xx) }
|
278
|
+
end
|
279
|
+
|
280
|
+
def test_long_options_also_take_equals
|
281
|
+
@p.opt :arg, "desc", :long => "arg", :type => String, :default => "hello"
|
282
|
+
opts = nil
|
283
|
+
assert_nothing_raised { opts = @p.parse %w() }
|
284
|
+
assert_equal "hello", opts[:arg]
|
285
|
+
assert_nothing_raised { opts = @p.parse %w(--arg goat) }
|
286
|
+
assert_equal "goat", opts[:arg]
|
287
|
+
assert_nothing_raised { opts = @p.parse %w(--arg=goat) }
|
288
|
+
assert_equal "goat", opts[:arg]
|
289
|
+
assert_raises(CommandlineError) { opts = @p.parse %w(--arg= goat) }
|
290
|
+
end
|
291
|
+
|
292
|
+
def test_auto_generated_long_names_convert_underscores_to_hyphens
|
293
|
+
@p.opt :hello_there
|
294
|
+
assert_equal "hello-there", @p.specs[:hello_there][:long]
|
295
|
+
end
|
296
|
+
|
297
|
+
end
|
298
|
+
|
299
|
+
end
|
300
|
+
end
|
metadata
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
rubygems_version: 0.8.11
|
3
|
+
specification_version: 1
|
4
|
+
name: trollop
|
5
|
+
version: !ruby/object:Gem::Version
|
6
|
+
version: "1.0"
|
7
|
+
date: 2007-01-29 00:00:00 -08:00
|
8
|
+
summary: Trollop is YAFCLAP --- yet another fine commandline argument processing library for Ruby. Trollop is designed to provide the maximal amount of GNU-style argument processing in the minimum number of lines of code (for you, the programmer).
|
9
|
+
require_paths:
|
10
|
+
- lib
|
11
|
+
email: wmorgan-trollop@masanjin.net
|
12
|
+
homepage: http://trollop.rubyforge.org
|
13
|
+
rubyforge_project: trollop
|
14
|
+
description: "Trollop is YAFCLAP --- yet another fine commandline argument processing library for Ruby. Trollop is designed to provide the maximal amount of GNU-style argument processing in the minimum number of lines of code (for you, the programmer). Trollop provides a nice automatically-generated help page, robust option parsing, and sensible defaults for everything you don't specify. Synopsis: ###### simple ###### opts = Trollop::options do opt :monkey, \"Use monkey mode.\" opt :goat, \"Use goat model\", :default => true opt :num_limbs, \"Set number of limbs\", :default => 4 end p opts ###### complex ###### opts = Trollop::options do version \"test 1.2.3 (c) 2007 William Morgan\" banner <<-EOS Test is an awesome program that does something very, very important. Usage: test [options] <filenames>+ where [options] are: EOS opt :ignore, \"Ignore incorrect values\" opt :file, \"Extra data filename to read in, with a very long option description like this one\", :type => String opt :volume, \"Volume level\", :default => 3.0 opt :iters, \"Number of iterations\", :default => 5 end Trollop::die :volume, \"must be non-negative\" if opts[:volume] < 0 Trollop::die :file, \"must exist\" unless File.exists?(opts[:file]) if opts[:file] == REQUIREMENTS: * none"
|
15
|
+
autorequire:
|
16
|
+
default_executable:
|
17
|
+
bindir: bin
|
18
|
+
has_rdoc: true
|
19
|
+
required_ruby_version: !ruby/object:Gem::Version::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">"
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.0.0
|
24
|
+
version:
|
25
|
+
platform: ruby
|
26
|
+
signing_key:
|
27
|
+
cert_chain:
|
28
|
+
authors:
|
29
|
+
- William Morgan
|
30
|
+
files:
|
31
|
+
- FAQ.txt
|
32
|
+
- History.txt
|
33
|
+
- Manifest.txt
|
34
|
+
- README.txt
|
35
|
+
- Rakefile
|
36
|
+
- lib/trollop.rb
|
37
|
+
- test/test_trollop.rb
|
38
|
+
test_files:
|
39
|
+
- test/test_trollop.rb
|
40
|
+
rdoc_options: []
|
41
|
+
|
42
|
+
extra_rdoc_files: []
|
43
|
+
|
44
|
+
executables: []
|
45
|
+
|
46
|
+
extensions: []
|
47
|
+
|
48
|
+
requirements: []
|
49
|
+
|
50
|
+
dependencies:
|
51
|
+
- !ruby/object:Gem::Dependency
|
52
|
+
name: hoe
|
53
|
+
version_requirement:
|
54
|
+
version_requirements: !ruby/object:Gem::Version::Requirement
|
55
|
+
requirements:
|
56
|
+
- - ">="
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
version: 1.1.7
|
59
|
+
version:
|