getopt 1.3.0 → 1.3.1
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/CHANGES +4 -0
- data/README +10 -4
- data/lib/getopt/long.rb +200 -0
- data/lib/getopt/std.rb +78 -0
- data/test/tc_getopt_long.rb +97 -1
- metadata +6 -2
data/CHANGES
CHANGED
@@ -1,3 +1,7 @@
|
|
1
|
+
== 18-Nov-2005 - 1.3.1
|
2
|
+
* Added support for compressed switches with getopt/long.
|
3
|
+
* More tests.
|
4
|
+
|
1
5
|
== 4-Nov-2005 - 1.3.0
|
2
6
|
* Added the Getopt::Long class (long.rb). This is a complete revamp of the
|
3
7
|
old getoptlong package, with ideas tossed in from Perl's Getopt::Long
|
data/README
CHANGED
@@ -117,19 +117,25 @@ INCREMENT
|
|
117
117
|
For example, if you do Getopt::Std.getopts("o:ID"), it will not parse
|
118
118
|
"-IDohello" properly. Instead, you must do "-IDo hello". Or, you can just
|
119
119
|
separate the argument, e.g. "-I -D -o hello".
|
120
|
+
|
121
|
+
=== Getopt::Long
|
122
|
+
If you mix and match compressed switches with separate, optional switches
|
123
|
+
the optional switch will be set to true instead of nil if it separated
|
124
|
+
from the compressed switches.
|
120
125
|
|
126
|
+
=== Reporting Bugs
|
121
127
|
If you find any other bugs, please log them on the project
|
122
128
|
page at http://www.rubyforge.org/projects/shards.
|
123
129
|
|
124
130
|
== Other Issues
|
125
|
-
Neither
|
126
|
-
|
131
|
+
Neither class attempts to be POSIX compliant in any way, shape or form.
|
132
|
+
And I don't care!
|
127
133
|
|
128
134
|
== Notes From the Author
|
129
135
|
My main gripe with the getoptlong library currently in the standard library
|
130
136
|
is that it doesn't return a hash, yet gives you partial hash behavior. This
|
131
|
-
was both confusing and annoying, since the first thing I (
|
132
|
-
|
137
|
+
was both confusing and annoying, since the first thing I do (along with
|
138
|
+
everyone else) is collect the results into a hash for later processing.
|
133
139
|
|
134
140
|
My main gripe with the optparse library (also in the standard library) is
|
135
141
|
that it treats command line processing like event processing. It's too
|
data/lib/getopt/long.rb
ADDED
@@ -0,0 +1,200 @@
|
|
1
|
+
module Getopt
|
2
|
+
|
3
|
+
REQUIRED = 0
|
4
|
+
BOOLEAN = 1
|
5
|
+
OPTIONAL = 2
|
6
|
+
INCREMENT = 3
|
7
|
+
NEGATABLE = 4
|
8
|
+
|
9
|
+
class LongError < StandardError; end
|
10
|
+
|
11
|
+
class Long
|
12
|
+
VERSION = "1.3.1"
|
13
|
+
|
14
|
+
# takes an array of switches. Each array consists of three elements.
|
15
|
+
def self.getopts(*switches)
|
16
|
+
hash = {} # Hash returned to user
|
17
|
+
valid = [] # Tracks valid switches
|
18
|
+
types = {} # Tracks argument types
|
19
|
+
syns = {} # Tracks long and short arguments, or multiple shorts
|
20
|
+
|
21
|
+
if switches.empty?
|
22
|
+
raise ArgumentError, "no switches provided"
|
23
|
+
end
|
24
|
+
|
25
|
+
# If a string is passed, split it and convert it to an array of arrays
|
26
|
+
if switches.first.kind_of?(String)
|
27
|
+
switches = switches.join.split
|
28
|
+
switches.map!{ |switch| switch = [switch] }
|
29
|
+
end
|
30
|
+
|
31
|
+
# Set our list of valid switches, and proper types for each switch
|
32
|
+
switches.each{ |switch|
|
33
|
+
valid.push(switch[0]) # Set valid long switches
|
34
|
+
|
35
|
+
# Set type for long switch, default to BOOLEAN
|
36
|
+
switch[2] ||= BOOLEAN
|
37
|
+
types[switch[0]] = switch[2]
|
38
|
+
|
39
|
+
# Create synonym hash. Default to first char of long switch for
|
40
|
+
# short switch, e.g. "--verbose" creates a "-v" synonym.
|
41
|
+
switch[1] ||= switch[0][1..2]
|
42
|
+
|
43
|
+
syns[switch[0]] = switch[1]
|
44
|
+
syns[switch[1]] = switch[0]
|
45
|
+
|
46
|
+
switch[1].each{ |char|
|
47
|
+
types[char] = switch[2] # Set type for short switch
|
48
|
+
valid.push(char) # Set valid short switches
|
49
|
+
}
|
50
|
+
}
|
51
|
+
|
52
|
+
re_long = /^(--\w+)$/
|
53
|
+
re_short = /^(-\w)$/
|
54
|
+
re_long_eq = /^(--\w+?)=(.*?)$|^(-\w?)=(.*?)$/
|
55
|
+
re_short_sq = /^(-\w)(\S+?)$/
|
56
|
+
|
57
|
+
ARGV.each_with_index{ |opt, index|
|
58
|
+
|
59
|
+
# Allow either -x -v or -xv style for sing char args
|
60
|
+
if re_short_sq.match(opt)
|
61
|
+
chars = opt.split("")[1..-1].map{ |s| s = "-#{s}" }
|
62
|
+
|
63
|
+
chars.each_with_index{ |char, i|
|
64
|
+
unless valid.include?(char)
|
65
|
+
raise LongError, "invalid switch '#{char}'"
|
66
|
+
end
|
67
|
+
|
68
|
+
# Grab the next arg if the switch takes a required arg
|
69
|
+
if types[char] == REQUIRED
|
70
|
+
# Deal with a argument squished up against switch
|
71
|
+
if chars[i+1]
|
72
|
+
arg = chars[i+1..-1].join.tr("-","")
|
73
|
+
ARGV.push(char, arg)
|
74
|
+
break
|
75
|
+
else
|
76
|
+
arg = ARGV.delete_at(index+1)
|
77
|
+
if arg.nil? || valid.include?(arg) # Minor cheat here
|
78
|
+
err = "no value provided for required argument '#{char}'"
|
79
|
+
raise LongError, err
|
80
|
+
end
|
81
|
+
ARGV.push(char, arg)
|
82
|
+
end
|
83
|
+
elsif types[char] == OPTIONAL
|
84
|
+
if chars[i+1] && !valid.include?(chars[i+1])
|
85
|
+
arg = chars[i+1..-1].join.tr("-","")
|
86
|
+
ARGV.push(char, arg)
|
87
|
+
break
|
88
|
+
elsif
|
89
|
+
if ARGV[index+1] && !valid.include?(ARGV[index+1])
|
90
|
+
arg = ARGV.delete_at(index+1)
|
91
|
+
ARGV.push(char, arg)
|
92
|
+
end
|
93
|
+
else
|
94
|
+
ARGV.push(char)
|
95
|
+
end
|
96
|
+
else
|
97
|
+
ARGV.push(char)
|
98
|
+
end
|
99
|
+
}
|
100
|
+
next
|
101
|
+
end
|
102
|
+
|
103
|
+
if match = re_long.match(opt) || match = re_short.match(opt)
|
104
|
+
switch = match.captures.first
|
105
|
+
end
|
106
|
+
|
107
|
+
if match = re_long_eq.match(opt)
|
108
|
+
switch, value = match.captures.compact
|
109
|
+
ARGV.push(switch, value)
|
110
|
+
next
|
111
|
+
end
|
112
|
+
|
113
|
+
# Make sure that all the switches are valid
|
114
|
+
unless valid.include?(switch)
|
115
|
+
raise LongError, "invalid switch '#{switch}'"
|
116
|
+
end
|
117
|
+
|
118
|
+
# Required arguments
|
119
|
+
if types[switch] == REQUIRED
|
120
|
+
nextval = ARGV[index+1]
|
121
|
+
|
122
|
+
# Make sure there's a value for mandatory arguments
|
123
|
+
if nextval.nil?
|
124
|
+
err = "no value provided for required argument '#{switch}'"
|
125
|
+
raise LongError, err
|
126
|
+
end
|
127
|
+
|
128
|
+
# If there is a value, make sure it's not another switch
|
129
|
+
if valid.include?(nextval)
|
130
|
+
err = "cannot pass switch '#{nextval}' as an argument"
|
131
|
+
raise LongError, err
|
132
|
+
end
|
133
|
+
|
134
|
+
# If the same option appears more than once, put the values
|
135
|
+
# in array.
|
136
|
+
if hash[switch]
|
137
|
+
hash[switch] = [hash[switch], nextval].flatten
|
138
|
+
else
|
139
|
+
hash[switch] = nextval
|
140
|
+
end
|
141
|
+
ARGV.delete_at(index+1)
|
142
|
+
end
|
143
|
+
|
144
|
+
# For boolean arguments set the switch's value to true.
|
145
|
+
if types[switch] == BOOLEAN
|
146
|
+
if hash.has_key?(switch)
|
147
|
+
raise LongError, "boolean switch already set"
|
148
|
+
end
|
149
|
+
hash[switch] = true
|
150
|
+
end
|
151
|
+
|
152
|
+
# For increment arguments, set the switch's value to 0, or
|
153
|
+
# increment it by one if it already exists.
|
154
|
+
if types[switch] == INCREMENT
|
155
|
+
if hash.has_key?(switch)
|
156
|
+
hash[switch] += 1
|
157
|
+
else
|
158
|
+
hash[switch] = 1
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
# For optional argument, there may be an argument. If so, it
|
163
|
+
# cannot be another switch. If not, it is set to true.
|
164
|
+
if types[switch] == OPTIONAL
|
165
|
+
nextval = ARGV[index+1]
|
166
|
+
if valid.include?(nextval)
|
167
|
+
hash[switch] = true
|
168
|
+
else
|
169
|
+
hash[switch] = nextval
|
170
|
+
ARGV.delete_at(index+1)
|
171
|
+
end
|
172
|
+
end
|
173
|
+
}
|
174
|
+
|
175
|
+
# Set synonymous switches to the same value, e.g. if -t is a synonym
|
176
|
+
# for --test, and the user passes "--test", then set "-t" to the same
|
177
|
+
# value that "--test" was set to.
|
178
|
+
#
|
179
|
+
# This allows users to refer to the long or short switch and get
|
180
|
+
# the same value
|
181
|
+
hash.each{ |switch, val|
|
182
|
+
if syns.keys.include?(switch)
|
183
|
+
syns[switch].each{ |key|
|
184
|
+
hash[key] = val
|
185
|
+
}
|
186
|
+
end
|
187
|
+
}
|
188
|
+
|
189
|
+
# Get rid of leading "--" and "-" to make it easier to reference
|
190
|
+
hash.each{ |key, value|
|
191
|
+
nkey = key.tr("-","")
|
192
|
+
hash.delete(key)
|
193
|
+
hash[nkey] = value
|
194
|
+
}
|
195
|
+
|
196
|
+
hash
|
197
|
+
end
|
198
|
+
|
199
|
+
end
|
200
|
+
end
|
data/lib/getopt/std.rb
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
module Getopt
|
2
|
+
class StdError < StandardError; end
|
3
|
+
class Std
|
4
|
+
VERSION = "1.3.0"
|
5
|
+
|
6
|
+
# Processes single character command line options with option
|
7
|
+
# clustering. This information is parsed from ARGV and returned
|
8
|
+
# as a hash, with the switch (minus the "-") as the key. The value
|
9
|
+
# for that key is either true/false (boolean switches) or the argument
|
10
|
+
# that was passed to the switch.
|
11
|
+
#
|
12
|
+
# Characters followed by a ":" require an argument. The rest are
|
13
|
+
# considered boolean switches. If a switch that accepts an argument
|
14
|
+
# appears more than once, the value for that key becomes an array
|
15
|
+
# of values.
|
16
|
+
#
|
17
|
+
def self.getopts(switches)
|
18
|
+
args = switches.split(/ */)
|
19
|
+
hash = {}
|
20
|
+
|
21
|
+
while !ARGV.empty? && ARGV.first =~ /^-(.)(.*)/s
|
22
|
+
first, rest = $1, $2
|
23
|
+
pos = switches.index(first)
|
24
|
+
|
25
|
+
# Switches on the command line must appear among the characters
|
26
|
+
# declared in +switches+.
|
27
|
+
raise StdError, "invalid option '#{first}'" unless pos
|
28
|
+
|
29
|
+
if args[pos+1] == ":"
|
30
|
+
ARGV.shift
|
31
|
+
if rest.empty?
|
32
|
+
rest = ARGV.shift
|
33
|
+
|
34
|
+
# Ensure that switches requiring arguments actually
|
35
|
+
# receive a (non-switch) argument.
|
36
|
+
if rest.nil? || rest.empty?
|
37
|
+
raise StdError, "missing argument for '-#{args[pos]}'"
|
38
|
+
end
|
39
|
+
|
40
|
+
# Do not permit switches that require arguments to be
|
41
|
+
# followed immediately by another switch.
|
42
|
+
if args.include?(rest) || args.include?(rest[1..-1])
|
43
|
+
err = "cannot use switch '#{rest}' as argument "
|
44
|
+
err += "to another switch"
|
45
|
+
raise StdError, err
|
46
|
+
end
|
47
|
+
|
48
|
+
# For non boolean switches, arguments that appear multiple
|
49
|
+
# times are converted to an array (or pushed onto an already
|
50
|
+
# existant array).
|
51
|
+
if hash.has_key?(first)
|
52
|
+
hash[first] = [hash[first], rest].flatten
|
53
|
+
else
|
54
|
+
hash[first] = rest
|
55
|
+
end
|
56
|
+
else
|
57
|
+
# Do not permit switches that require arguments to be
|
58
|
+
# followed immediately by another switch.
|
59
|
+
if args.include?(rest) || args.include?(rest[1..-1])
|
60
|
+
err = "cannot use switch '#{rest}' as argument "
|
61
|
+
err += "to another switch"
|
62
|
+
raise StdError, err
|
63
|
+
end
|
64
|
+
end
|
65
|
+
else
|
66
|
+
hash[first] = true # Boolean switch
|
67
|
+
if rest.empty?
|
68
|
+
ARGV.shift
|
69
|
+
else
|
70
|
+
ARGV[0] = "-#{rest}"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
hash
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
data/test/tc_getopt_long.rb
CHANGED
@@ -21,7 +21,7 @@ class TC_Getopt_Long < Test::Unit::TestCase
|
|
21
21
|
end
|
22
22
|
|
23
23
|
def test_version
|
24
|
-
assert_equal("1.3.
|
24
|
+
assert_equal("1.3.1", Long::VERSION)
|
25
25
|
end
|
26
26
|
|
27
27
|
def test_constants
|
@@ -112,6 +112,102 @@ class TC_Getopt_Long < Test::Unit::TestCase
|
|
112
112
|
assert_equal({"foo"=>"1", "bar"=>"hello", "f"=>"1", "b"=>"hello"}, @opts)
|
113
113
|
end
|
114
114
|
|
115
|
+
def test_compressed_switches
|
116
|
+
ARGV.push("-fb")
|
117
|
+
assert_nothing_raised{
|
118
|
+
@opts = Long.getopts(
|
119
|
+
["--foo", "-f", BOOLEAN],
|
120
|
+
["--bar", "-b", BOOLEAN]
|
121
|
+
)
|
122
|
+
}
|
123
|
+
assert_equal({"foo"=>true, "f"=>true, "b"=>true, "bar"=>true}, @opts)
|
124
|
+
end
|
125
|
+
|
126
|
+
def test_compress_switches_with_required_arg
|
127
|
+
ARGV.push("-xf", "foo.txt")
|
128
|
+
assert_nothing_raised{
|
129
|
+
@opts = Long.getopts(
|
130
|
+
["--expand", "-x", BOOLEAN],
|
131
|
+
["--file", "-f", REQUIRED]
|
132
|
+
)
|
133
|
+
}
|
134
|
+
assert_equal(
|
135
|
+
{"x"=>true, "expand"=>true, "f"=>"foo.txt", "file"=>"foo.txt"}, @opts
|
136
|
+
)
|
137
|
+
end
|
138
|
+
|
139
|
+
def test_compress_switches_with_compressed_required_arg
|
140
|
+
ARGV.push("-xffoo.txt")
|
141
|
+
assert_nothing_raised{
|
142
|
+
@opts = Long.getopts(
|
143
|
+
["--expand", "-x", BOOLEAN],
|
144
|
+
["--file", "-f", REQUIRED]
|
145
|
+
)
|
146
|
+
}
|
147
|
+
assert_equal(
|
148
|
+
{"x"=>true, "expand"=>true, "f"=>"foo.txt", "file"=>"foo.txt"}, @opts
|
149
|
+
)
|
150
|
+
end
|
151
|
+
|
152
|
+
def test_compress_switches_with_optional_arg_not_defined
|
153
|
+
ARGV.push("-xf")
|
154
|
+
assert_nothing_raised{
|
155
|
+
@opts = Long.getopts(
|
156
|
+
["--expand", "-x", BOOLEAN],
|
157
|
+
["--file", "-f", OPTIONAL]
|
158
|
+
)
|
159
|
+
}
|
160
|
+
assert_equal(
|
161
|
+
{"x"=>true, "expand"=>true, "f"=>nil, "file"=>nil}, @opts
|
162
|
+
)
|
163
|
+
end
|
164
|
+
|
165
|
+
def test_compress_switches_with_optional_arg
|
166
|
+
ARGV.push("-xf", "boo.txt")
|
167
|
+
assert_nothing_raised{
|
168
|
+
@opts = Long.getopts(
|
169
|
+
["--expand", "-x", BOOLEAN],
|
170
|
+
["--file", "-f", OPTIONAL]
|
171
|
+
)
|
172
|
+
}
|
173
|
+
assert_equal(
|
174
|
+
{"x"=>true, "expand"=>true, "f"=>"boo.txt", "file"=>"boo.txt"}, @opts
|
175
|
+
)
|
176
|
+
end
|
177
|
+
|
178
|
+
def test_compress_switches_with_compressed_optional_arg
|
179
|
+
ARGV.push("-xfboo.txt")
|
180
|
+
assert_nothing_raised{
|
181
|
+
@opts = Long.getopts(
|
182
|
+
["--expand", "-x", BOOLEAN],
|
183
|
+
["--file", "-f", OPTIONAL]
|
184
|
+
)
|
185
|
+
}
|
186
|
+
assert_equal(
|
187
|
+
{"x"=>true, "expand"=>true, "f"=>"boo.txt", "file"=>"boo.txt"}, @opts
|
188
|
+
)
|
189
|
+
end
|
190
|
+
|
191
|
+
def test_compressed_short_and_long_mixed
|
192
|
+
ARGV.push("-xb", "--file", "boo.txt", "-v")
|
193
|
+
assert_nothing_raised{
|
194
|
+
@opts = Long.getopts(
|
195
|
+
["--expand", "-x", BOOLEAN],
|
196
|
+
["--verbose", "-v", BOOLEAN],
|
197
|
+
["--file", "-f", REQUIRED],
|
198
|
+
["--bar", "-b", OPTIONAL]
|
199
|
+
)
|
200
|
+
assert_equal(
|
201
|
+
{ "x"=>true, "expand"=>true,
|
202
|
+
"v"=>true, "verbose"=>true,
|
203
|
+
"f"=>"boo.txt", "file"=>"boo.txt",
|
204
|
+
"b"=>nil, "bar"=>nil
|
205
|
+
},
|
206
|
+
@opts
|
207
|
+
)
|
208
|
+
}
|
209
|
+
end
|
210
|
+
|
115
211
|
def teardown
|
116
212
|
@opts = nil
|
117
213
|
ARGV.clear
|
metadata
CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.8.11
|
|
3
3
|
specification_version: 1
|
4
4
|
name: getopt
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 1.3.
|
7
|
-
date: 2005-11-
|
6
|
+
version: 1.3.1
|
7
|
+
date: 2005-11-18 00:00:00 -07:00
|
8
8
|
summary: Getopt::Std for Ruby
|
9
9
|
require_paths:
|
10
10
|
- lib
|
@@ -29,12 +29,16 @@ cert_chain:
|
|
29
29
|
authors:
|
30
30
|
- Daniel J. Berger
|
31
31
|
files:
|
32
|
+
- lib/getopt/long.rb
|
33
|
+
- lib/getopt/std.rb
|
32
34
|
- CHANGES
|
33
35
|
- MANIFEST
|
34
36
|
- README
|
37
|
+
- CVS
|
35
38
|
- test/tc_getopt_long.rb
|
36
39
|
- test/tc_getopt_std.rb
|
37
40
|
- test/ts_all.rb
|
41
|
+
- test/CVS
|
38
42
|
test_files:
|
39
43
|
- test/ts_all.rb
|
40
44
|
rdoc_options: []
|