getopt 1.3.0 → 1.3.1

Sign up to get free protection for your applications and to get access to all the features.
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 of the classes attempts to be POSIX compliant in any way, shape
126
- or form. And I don't care!
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 (and everyone else)
132
- does is collect the results into a hash for later processing.
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
@@ -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
@@ -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.0", Long::VERSION)
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.0
7
- date: 2005-11-04 00:00:00 -07:00
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: []