trollop 1.13 → 1.14
Sign up to get free protection for your applications and to get access to all the features.
- data/FAQ.txt +66 -17
- data/History.txt +4 -0
- data/lib/trollop.rb +35 -9
- data/test/test_trollop.rb +47 -7
- metadata +3 -3
data/FAQ.txt
CHANGED
@@ -1,11 +1,14 @@
|
|
1
1
|
Trollop FAQ
|
2
2
|
-----------
|
3
3
|
|
4
|
+
Q: Why is it called "Trollop"?
|
5
|
+
A: No reason.
|
6
|
+
|
4
7
|
Q: Why should I use Trollop?
|
5
|
-
A: Because it will take you FEWER LINES OF CODE to do reasonable option
|
6
|
-
|
8
|
+
A: Because it will take you FEWER LINES OF CODE to do reasonable option parsing
|
9
|
+
than any other option parser out there.
|
7
10
|
|
8
|
-
Look:
|
11
|
+
Look at this:
|
9
12
|
|
10
13
|
opts = Trollop::options do
|
11
14
|
opt :monkey, "Use monkey mode"
|
@@ -15,21 +18,67 @@ A: Because it will take you FEWER LINES OF CODE to do reasonable option
|
|
15
18
|
|
16
19
|
That's it. And opts is a hash and you do whatever you want with it.
|
17
20
|
Trivial. You don't have to mix option processing code blocks with the
|
18
|
-
declarations. You don't have to make a class for every option (what is
|
19
|
-
|
20
|
-
option.
|
21
|
+
declarations. You don't have to make a class for every option (what is this,
|
22
|
+
Java?). You don't have to write more than 1 line of code per option.
|
21
23
|
|
22
|
-
|
23
|
-
|
24
|
+
Plus, you get a beautiful help screen that detects your terminal width and
|
25
|
+
wraps appropriately. C'mon, that's hot.
|
26
|
+
|
27
|
+
Q: What is the philosophy behind Trollop?
|
28
|
+
A: Must a commandline option processor have a philosophy?
|
29
|
+
|
30
|
+
Q: Seriously now. What is it?
|
31
|
+
A: Ok, it's this: Trollop *just* does the parsing and gives you a hash table
|
32
|
+
of the result. So whatever fancy logic or constraints you need, you can
|
33
|
+
implement by operating on that hash table. Options that disable other
|
34
|
+
options, fancy constraints involving multiple sets of values across multiple
|
35
|
+
sets of options, etc. are all left for you to do manually.
|
36
|
+
|
37
|
+
(Trollop does support limited constraint setting (see #conflicts and
|
38
|
+
#depends), but any non-trivial program will need to get fancier.)
|
39
|
+
|
40
|
+
The result is that using Trollop is pretty simple, and whatever bizarre
|
41
|
+
logic you want, you can write yourself. And don't forget, you can call
|
42
|
+
Trollop::die to abort the program and give a fancy options-related error
|
43
|
+
message.
|
44
|
+
|
45
|
+
Q: What happens to the other stuff on the commandline?
|
46
|
+
A: Anything Trollop doesn't recognize as an option or as an option parameter is
|
47
|
+
left in ARGV for you to process.
|
48
|
+
|
49
|
+
Q: Does Trollop support multiple-value arguments?
|
50
|
+
A: Yes. If you set the :type of an option to something plural, like ":ints",
|
51
|
+
":strings", ":doubles", ":floats", ":ios", it will accept multiple arguments
|
52
|
+
on the commandline and the value will be an array of these.
|
53
|
+
|
54
|
+
Q: Does Trollop support arguments that can be given multiple times?
|
55
|
+
A: Yes. If you set :multi to true, then the argument can appear multiple times
|
56
|
+
on the commandline, and the value will be an array of the parameters.
|
57
|
+
|
58
|
+
Q: Does Trollop support subcommands?
|
59
|
+
A: Yes. You get subcommand support by adding a call #stop_on within the options
|
60
|
+
block, and passing the names of the subcommands to it. (See the third
|
61
|
+
example on the webpage.) When Trollop encounters one of these subcommands on
|
62
|
+
the commandline, it stops processing and returns.
|
63
|
+
|
64
|
+
ARGV at that point will contain the subcommand followed by any subcommand
|
65
|
+
options, since Trollop has contained the rest. So you can consume the
|
66
|
+
subcommand and call Trollop.options again with the particular options set
|
67
|
+
for that subcommand.
|
68
|
+
|
69
|
+
If you don't know the subcommands ahead of time, you can call
|
70
|
+
#stop_on_unknown, which will cause Trollop to stop when it encounters any
|
71
|
+
unknown token. This might be more trouble than its worth if you're also
|
72
|
+
passing filenames on the commandline.
|
24
73
|
|
25
|
-
|
26
|
-
|
27
|
-
A: Because it's ambiguous whether these are arguments or negative
|
28
|
-
integer or floating-point parameters to arguments. E.g., what
|
29
|
-
about "-f -3". Is that a negative three parameter to -f, or two
|
30
|
-
separate parameters?
|
74
|
+
It's probably easier to see the example on the webpage than to read all
|
75
|
+
that.
|
31
76
|
|
32
|
-
|
33
|
-
|
34
|
-
|
77
|
+
Q: Why does Trollop disallow numeric short argument names, like '-1' and '-9'?
|
78
|
+
A: Because it's ambiguous whether these are arguments or negative integer or
|
79
|
+
floating-point parameters to arguments. E.g., what about "-f -3". Is that a
|
80
|
+
negative three parameter to -f, or two separate parameters?
|
35
81
|
|
82
|
+
I could be very clever and detect when there are no arguments that require
|
83
|
+
floating-point parameters, and allow such short option names in those cases,
|
84
|
+
but opted for simplicity and consistency.
|
data/History.txt
CHANGED
data/lib/trollop.rb
CHANGED
@@ -3,9 +3,11 @@
|
|
3
3
|
## Copyright:: Copyright 2007 William Morgan
|
4
4
|
## License:: GNU GPL version 2
|
5
5
|
|
6
|
+
require 'date'
|
7
|
+
|
6
8
|
module Trollop
|
7
9
|
|
8
|
-
VERSION = "1.
|
10
|
+
VERSION = "1.14"
|
9
11
|
|
10
12
|
## Thrown by Parser in the event of a commandline error. Not needed if
|
11
13
|
## you're using the Trollop::options entry.
|
@@ -39,16 +41,17 @@ class Parser
|
|
39
41
|
## +:type+ parameter of #opt.
|
40
42
|
FLAG_TYPES = [:flag, :bool, :boolean]
|
41
43
|
|
42
|
-
## The set of values that indicate a single-parameter option when
|
44
|
+
## The set of values that indicate a single-parameter (normal) option when
|
43
45
|
## passed as the +:type+ parameter of #opt.
|
44
46
|
##
|
45
47
|
## A value of +io+ corresponds to a readable IO resource, including
|
46
48
|
## a filename, URI, or the strings 'stdin' or '-'.
|
47
|
-
SINGLE_ARG_TYPES = [:int, :integer, :string, :double, :float, :io]
|
49
|
+
SINGLE_ARG_TYPES = [:int, :integer, :string, :double, :float, :io, :date]
|
48
50
|
|
49
|
-
## The set of values that indicate a multiple-parameter option
|
50
|
-
##
|
51
|
-
|
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]
|
52
55
|
|
53
56
|
## The complete set of legal values for the +:type+ parameter of #opt.
|
54
57
|
TYPES = FLAG_TYPES + SINGLE_ARG_TYPES + MULTI_ARG_TYPES
|
@@ -104,6 +107,8 @@ class Parser
|
|
104
107
|
##
|
105
108
|
## Arguments that can occur multiple times should be marked with
|
106
109
|
## +:multi+ => +true+. The value of this argument will also be an array.
|
110
|
+
## In contrast with regular non-multi options, if not specified on
|
111
|
+
## the commandline, the default value will be [], not nil.
|
107
112
|
##
|
108
113
|
## These two attributes can be combined (e.g. +:type+ => +:strings+,
|
109
114
|
## +:multi+ => +true+), in which case the value of the argument will be
|
@@ -128,12 +133,13 @@ class Parser
|
|
128
133
|
when :double; :float
|
129
134
|
when :doubles; :floats
|
130
135
|
when Class
|
131
|
-
case opts[:type].
|
136
|
+
case opts[:type].name
|
132
137
|
when 'TrueClass', 'FalseClass'; :flag
|
133
138
|
when 'String'; :string
|
134
139
|
when 'Integer'; :int
|
135
140
|
when 'Float'; :float
|
136
141
|
when 'IO'; :io
|
142
|
+
when 'Date'; :date
|
137
143
|
else
|
138
144
|
raise ArgumentError, "unsupported argument type '#{opts[:type].class.name}'"
|
139
145
|
end
|
@@ -161,6 +167,7 @@ class Parser
|
|
161
167
|
when TrueClass, FalseClass; :flag
|
162
168
|
when String; :string
|
163
169
|
when IO; :io
|
170
|
+
when Date; :date
|
164
171
|
when Array
|
165
172
|
if opts[:default].empty?
|
166
173
|
raise ArgumentError, "multiple argument type cannot be deduced from an empty array for '#{opts[:default][0].class.name}'"
|
@@ -170,6 +177,7 @@ class Parser
|
|
170
177
|
when Numeric; :floats
|
171
178
|
when String; :strings
|
172
179
|
when IO; :ios
|
180
|
+
when Date; :dates
|
173
181
|
else
|
174
182
|
raise ArgumentError, "unsupported multiple argument type '#{opts[:default][0].class.name}'"
|
175
183
|
end
|
@@ -178,7 +186,7 @@ class Parser
|
|
178
186
|
raise ArgumentError, "unsupported argument type '#{opts[:default].class.name}'"
|
179
187
|
end
|
180
188
|
|
181
|
-
raise ArgumentError, ":type specification and default type don't match" if opts[:type] && type_from_default && opts[:type] != type_from_default
|
189
|
+
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
|
182
190
|
|
183
191
|
opts[:type] = opts[:type] || type_from_default || :flag
|
184
192
|
|
@@ -280,6 +288,7 @@ class Parser
|
|
280
288
|
@specs.each do |sym, opts|
|
281
289
|
required[sym] = true if opts[:required]
|
282
290
|
vals[sym] = opts[:default]
|
291
|
+
vals[sym] = [] if opts[:multi] && !opts[:default] # multi arguments default to [], not nil
|
283
292
|
end
|
284
293
|
|
285
294
|
resolve_default_short_options
|
@@ -364,6 +373,8 @@ class Parser
|
|
364
373
|
vals[sym] = params.map { |pg| pg.map { |p| p.to_s } }
|
365
374
|
when :io, :ios
|
366
375
|
vals[sym] = params.map { |pg| pg.map { |p| parse_io_parameter p, arg } }
|
376
|
+
when :date, :dates
|
377
|
+
vals[sym] = params.map { |pg| pg.map { |p| parse_date_parameter p, arg } }
|
367
378
|
end
|
368
379
|
|
369
380
|
if SINGLE_ARG_TYPES.include?(opts[:type])
|
@@ -387,6 +398,19 @@ class Parser
|
|
387
398
|
vals
|
388
399
|
end
|
389
400
|
|
401
|
+
def parse_date_parameter param, arg #:nodoc:
|
402
|
+
begin
|
403
|
+
begin
|
404
|
+
time = Chronic.parse(param)
|
405
|
+
rescue NameError
|
406
|
+
# chronic is not available
|
407
|
+
end
|
408
|
+
time ? Date.new(time.year, time.month, time.day) : Date.parse(param)
|
409
|
+
rescue ArgumentError => e
|
410
|
+
raise CommandlineError, "option '#{arg}' needs a date"
|
411
|
+
end
|
412
|
+
end
|
413
|
+
|
390
414
|
## Print the help message to +stream+.
|
391
415
|
def educate stream=$stdout
|
392
416
|
width # just calculate it now; otherwise we have to be careful not to
|
@@ -406,6 +430,8 @@ class Parser
|
|
406
430
|
when :floats; " <f+>"
|
407
431
|
when :io; " <filename/uri>"
|
408
432
|
when :ios; " <filename/uri+>"
|
433
|
+
when :date; " <date>"
|
434
|
+
when :dates; " <date+>"
|
409
435
|
end
|
410
436
|
end
|
411
437
|
|
@@ -587,7 +613,7 @@ private
|
|
587
613
|
opts = @specs[name]
|
588
614
|
next if opts[:short]
|
589
615
|
|
590
|
-
c = opts[:long].split(//).find { |
|
616
|
+
c = opts[:long].split(//).find { |d| d !~ INVALID_SHORT_ARG_REGEX && !@short.member?(d) }
|
591
617
|
raise ArgumentError, "can't generate a default short option name for #{opts[:long].inspect}: out of unique characters" unless c
|
592
618
|
|
593
619
|
opts[:short] = c
|
data/test/test_trollop.rb
CHANGED
@@ -99,6 +99,15 @@ class Trollop < ::Test::Unit::TestCase
|
|
99
99
|
assert_equal 2, opts["argsf"]
|
100
100
|
assert_raise(CommandlineError) { @p.parse(%w(--argsf hello)) }
|
101
101
|
|
102
|
+
# single arg: date
|
103
|
+
date = Date.today
|
104
|
+
assert_nothing_raised { @p.opt "argsd", "desc", :default => date }
|
105
|
+
assert_nothing_raised { opts = @p.parse("--") }
|
106
|
+
assert_equal Date.today, opts["argsd"]
|
107
|
+
assert_nothing_raised { opts = @p.parse(['--argsd', 'Jan 4, 2007']) }
|
108
|
+
assert_equal Date.civil(2007, 1, 4), opts["argsd"]
|
109
|
+
assert_raise(CommandlineError) { @p.parse(%w(--argsd hello)) }
|
110
|
+
|
102
111
|
# single arg: string
|
103
112
|
assert_nothing_raised { @p.opt "argss", "desc", :default => "foobar" }
|
104
113
|
assert_nothing_raised { opts = @p.parse("--") }
|
@@ -127,14 +136,23 @@ class Trollop < ::Test::Unit::TestCase
|
|
127
136
|
assert_equal [4.0], opts["argmf"]
|
128
137
|
assert_raise(CommandlineError) { @p.parse(%w(--argmf hello)) }
|
129
138
|
|
139
|
+
# multi args: dates
|
140
|
+
dates = [Date.today, Date.civil(2007, 1, 4)]
|
141
|
+
assert_nothing_raised { @p.opt "argmd", "desc", :default => dates }
|
142
|
+
assert_nothing_raised { opts = @p.parse("--") }
|
143
|
+
assert_equal dates, opts["argmd"]
|
144
|
+
assert_nothing_raised { opts = @p.parse(['--argmd', 'Jan 4, 2007']) }
|
145
|
+
assert_equal [Date.civil(2007, 1, 4)], opts["argmd"]
|
146
|
+
assert_raise(CommandlineError) { @p.parse(%w(--argmd hello)) }
|
147
|
+
|
130
148
|
# multi args: strings
|
131
|
-
assert_nothing_raised { @p.opt "
|
149
|
+
assert_nothing_raised { @p.opt "argmst", "desc", :default => %w(hello world) }
|
132
150
|
assert_nothing_raised { opts = @p.parse("--") }
|
133
|
-
assert_equal %w(hello world), opts["
|
134
|
-
assert_nothing_raised { opts = @p.parse(%w(--
|
135
|
-
assert_equal ["3.4"], opts["
|
136
|
-
assert_nothing_raised { opts = @p.parse(%w(--
|
137
|
-
assert_equal ["goodbye"], opts["
|
151
|
+
assert_equal %w(hello world), opts["argmst"]
|
152
|
+
assert_nothing_raised { opts = @p.parse(%w(--argmst 3.4)) }
|
153
|
+
assert_equal ["3.4"], opts["argmst"]
|
154
|
+
assert_nothing_raised { opts = @p.parse(%w(--argmst goodbye)) }
|
155
|
+
assert_equal ["goodbye"], opts["argmst"]
|
138
156
|
end
|
139
157
|
|
140
158
|
## :type and :default must match if both are specified
|
@@ -146,10 +164,12 @@ class Trollop < ::Test::Unit::TestCase
|
|
146
164
|
|
147
165
|
assert_nothing_raised { @p.opt "argsi", "desc", :type => :int, :default => 4 }
|
148
166
|
assert_nothing_raised { @p.opt "argsf", "desc", :type => :float, :default => 3.14 }
|
167
|
+
assert_nothing_raised { @p.opt "argsd", "desc", :type => :date, :default => Date.today }
|
149
168
|
assert_nothing_raised { @p.opt "argss", "desc", :type => :string, :default => "yo" }
|
150
169
|
assert_nothing_raised { @p.opt "argmi", "desc", :type => :ints, :default => [4] }
|
151
170
|
assert_nothing_raised { @p.opt "argmf", "desc", :type => :floats, :default => [3.14] }
|
152
|
-
assert_nothing_raised { @p.opt "
|
171
|
+
assert_nothing_raised { @p.opt "argmd", "desc", :type => :dates, :default => [Date.today] }
|
172
|
+
assert_nothing_raised { @p.opt "argmst", "desc", :type => :strings, :default => ["yo"] }
|
153
173
|
end
|
154
174
|
|
155
175
|
def test_long_detects_bad_names
|
@@ -352,6 +372,20 @@ EOM
|
|
352
372
|
assert_raises(CommandlineError) { @p.parse %w(-f -.) }
|
353
373
|
end
|
354
374
|
|
375
|
+
def test_date_formatting
|
376
|
+
@p.opt :arg, "desc", :type => :date, :short => 'd'
|
377
|
+
opts = nil
|
378
|
+
assert_nothing_raised { opts = @p.parse(['-d', 'Jan 4, 2007']) }
|
379
|
+
assert_equal Date.civil(2007, 1, 4), opts[:arg]
|
380
|
+
begin
|
381
|
+
require 'chronic'
|
382
|
+
assert_nothing_raised { opts = @p.parse(['-d', 'today']) }
|
383
|
+
assert_equal Date.today, opts[:arg]
|
384
|
+
rescue LoadError
|
385
|
+
# chronic is not available
|
386
|
+
end
|
387
|
+
end
|
388
|
+
|
355
389
|
def test_short_options_cant_be_numeric
|
356
390
|
assert_raises(ArgumentError) { @p.opt :arg, "desc", :short => "-1" }
|
357
391
|
@p.opt :a1b, "desc"
|
@@ -1002,6 +1036,12 @@ EOM
|
|
1002
1036
|
assert_equal "hello there", opts[:arg2]
|
1003
1037
|
assert_equal 0, @p.leftovers.size
|
1004
1038
|
end
|
1039
|
+
|
1040
|
+
def test_multi_args_default_to_empty_array
|
1041
|
+
@p.opt :arg1, "arg", :multi => true
|
1042
|
+
opts = @p.parse ""
|
1043
|
+
assert_equal [], opts[:arg1]
|
1044
|
+
end
|
1005
1045
|
end
|
1006
1046
|
|
1007
1047
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: trollop
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: "1.
|
4
|
+
version: "1.14"
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- William Morgan
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-
|
12
|
+
date: 2009-06-19 00:00:00 -04:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|
@@ -55,7 +55,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
55
55
|
requirements: []
|
56
56
|
|
57
57
|
rubyforge_project: trollop
|
58
|
-
rubygems_version: 1.
|
58
|
+
rubygems_version: 1.3.1
|
59
59
|
signing_key:
|
60
60
|
specification_version: 2
|
61
61
|
summary: Trollop is a commandline option parser for Ruby that just gets out of your way. One line of code per option is all you need to write. For that, you get a nice automatically-generated help page, robust option parsing, command subcompletion, and sensible defaults for everything you don't specify.
|