Skip to content

Commit b936d46

Browse files
committed
add positional args test
1 parent 34187e6 commit b936d46

File tree

3 files changed

+100
-27
lines changed

3 files changed

+100
-27
lines changed

README.md

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
# A minimal POSIX getopt(3) implementation in Zig
1+
# Minimal POSIX getopt(3) implementation in Zig
22

3-
This is a minimal getopt(3) implementation with [POSIX-conforming](http://pubs.opengroup.org/onlinepubs/9699919799/functions/getopt.html) argument parsing semantics.
3+
This is a minimal, allocation-free getopt(3) implementation with [POSIX-conforming](http://pubs.opengroup.org/onlinepubs/9699919799/functions/getopt.html) argument parsing semantics.
44

55
## Example
66

@@ -35,26 +35,19 @@ pub fn main() void {
3535
} else break;
3636
} else |err| {
3737
switch (err) {
38-
getopt.Error.InvalidOption => debug.print("invalid option: {}\n", .{opts.optopt}),
39-
getopt.Error.MissingArgument => debug.print("option requires an argument: {}\n", .{opts.optopt}),
38+
getopt.Error.InvalidOption => debug.print("invalid option: {c}\n", .{opts.optopt}),
39+
getopt.Error.MissingArgument => debug.print("option requires an argument: {c}\n", .{opts.optopt}),
4040
}
4141
}
42+
43+
debug.print("remaining args: {s}\n", .{opts.args()});
4244
}
4345
```
4446

4547
```
46-
$ zig run example.zig -- -hv -a42
48+
$ zig run example.zig -- -hv -a42 foo bar
4749
usage: example [-a arg] [-hv]
4850
verbose = true
4951
arg = 42
50-
```
51-
52-
```
53-
$ zig run example.zig -- -w
54-
invalid option: w
55-
```
56-
57-
```
58-
$ zig run example.zig -- -a
59-
option requires an argument: a
52+
remaining args: { foo, bar }
6053
```

example.zig

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,6 @@ pub fn main() void {
3232
getopt.Error.MissingArgument => debug.print("option requires an argument: {c}\n", .{opts.optopt}),
3333
}
3434
}
35+
36+
debug.print("remaining args: {s}\n", .{opts.args()});
3537
}

getopt.zig

Lines changed: 90 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,12 @@ const mem = std.mem;
44
const os = std.os;
55
const expect = std.testing.expect;
66

7+
/// Parsed option struct.
78
pub const Option = struct {
9+
/// Option character.
810
opt: u8,
11+
12+
/// Option argument, if any.
913
arg: ?[]const u8 = null,
1014
};
1115

@@ -15,8 +19,12 @@ pub const OptionsIterator = struct {
1519
argv: [][*:0]const u8,
1620
opts: []const u8,
1721

22+
/// Index of the current element of the argv vector.
1823
optind: usize = 1,
24+
1925
optpos: usize = 1,
26+
27+
/// Current option character.
2028
optopt: u8 = undefined,
2129

2230
pub fn next(self: *OptionsIterator) Error!?Option {
@@ -35,13 +43,13 @@ pub const OptionsIterator = struct {
3543

3644
self.optopt = arg[self.optpos];
3745

38-
const maybe_i = mem.indexOfScalar(u8, self.opts, self.optopt);
39-
if (maybe_i) |i| {
40-
if (i < self.opts.len - 1 and self.opts[i + 1] == ':') {
46+
const maybe_idx = mem.indexOfScalar(u8, self.opts, self.optopt);
47+
if (maybe_idx) |idx| {
48+
if (idx < self.opts.len - 1 and self.opts[idx + 1] == ':') {
4149
if (arg[self.optpos + 1] != 0) {
4250
const res = Option{
4351
.opt = self.optopt,
44-
.arg = mem.sliceTo(arg + self.optpos + 1, 0),
52+
.arg = mem.span(arg + self.optpos + 1),
4553
};
4654
self.optind += 1;
4755
self.optpos = 1;
@@ -66,22 +74,27 @@ pub const OptionsIterator = struct {
6674
} else return Error.InvalidOption;
6775
}
6876

69-
pub fn args(self: *OptionsIterator) [][*:0]const u8 {
70-
return argv[self.optind..];
77+
/// Return remaining arguments, if any.
78+
pub fn args(self: *OptionsIterator) ?[][*:0]const u8 {
79+
if (self.optind < self.argv.len)
80+
return self.argv[self.optind..]
81+
else
82+
return null;
7183
}
7284
};
7385

74-
fn getoptArgv(argv: [][*:0]const u8, opts: []const u8) OptionsIterator {
86+
fn getoptArgv(argv: [][*:0]const u8, optstring: []const u8) OptionsIterator {
7587
return OptionsIterator{
7688
.argv = argv,
77-
.opts = opts,
89+
.opts = optstring,
7890
};
7991
}
8092

81-
pub fn getopt(opts: []const u8) OptionsIterator {
93+
/// Parse os.argv according to the optstring.
94+
pub fn getopt(optstring: []const u8) OptionsIterator {
8295
// https://github.com/ziglang/zig/issues/8808
8396
const argv: [][*:0]const u8 = os.argv;
84-
return getoptArgv(argv, opts);
97+
return getoptArgv(argv, optstring);
8598
}
8699

87100
test "no args separate" {
@@ -107,6 +120,8 @@ test "no args separate" {
107120
try expect(opt.arg == null and expected[i].arg == null);
108121
}
109122
}
123+
124+
try expect(opts.args() == null);
110125
}
111126

112127
test "no args joined" {
@@ -216,7 +231,7 @@ test "invalid option" {
216231
if (maybe_opt) {
217232
unreachable;
218233
} else |err| {
219-
try expect(err == OptionError.InvalidOption);
234+
try expect(err == Error.InvalidOption);
220235
try expect(opts.optopt == 'z');
221236
}
222237
}
@@ -236,7 +251,70 @@ test "missing argument" {
236251
if (maybe_opt) {
237252
unreachable;
238253
} else |err| {
239-
try expect(err == OptionError.MissingArgument);
254+
try expect(err == Error.MissingArgument);
240255
try expect(opts.optopt == 'z');
241256
}
242257
}
258+
259+
test "positional args" {
260+
var argv = [_][*:0]const u8{
261+
"getopt",
262+
"-abc10",
263+
"-d",
264+
"foo",
265+
"bar",
266+
};
267+
268+
const expected = [_]Option{
269+
.{ .opt = 'a' },
270+
.{ .opt = 'b' },
271+
.{
272+
.opt = 'c',
273+
.arg = "10",
274+
},
275+
.{ .opt = 'd' },
276+
};
277+
278+
var opts = getoptArgv(&argv, "abc:d");
279+
280+
var i: usize = 0;
281+
while (try opts.next()) |opt| : (i += 1) {
282+
try expect(opt.opt == expected[i].opt);
283+
if (opt.arg != null and expected[i].arg != null) {
284+
try expect(mem.eql(u8, opt.arg.?, expected[i].arg.?));
285+
} else {
286+
try expect(opt.arg == null and expected[i].arg == null);
287+
}
288+
}
289+
290+
try expect(mem.eql([*:0]const u8, opts.args().?, &[_][*:0]const u8{ "foo", "bar" }));
291+
}
292+
293+
test "positional args with separator" {
294+
var argv = [_][*:0]const u8{
295+
"getopt",
296+
"-ab",
297+
"--",
298+
"foo",
299+
"bar",
300+
};
301+
302+
const expected = [_]Option{
303+
.{ .opt = 'a' },
304+
.{ .opt = 'b' },
305+
};
306+
307+
var opts = getoptArgv(&argv, "ab");
308+
309+
var i: usize = 0;
310+
while (try opts.next()) |opt| : (i += 1) {
311+
try expect(opt.opt == expected[i].opt);
312+
if (opt.arg != null and expected[i].arg != null) {
313+
try expect(mem.eql(u8, opt.arg.?, expected[i].arg.?));
314+
} else {
315+
try expect(opt.arg == null and expected[i].arg == null);
316+
}
317+
}
318+
319+
try expect(mem.eql([*:0]const u8, opts.args().?, &[_][*:0]const u8{ "foo", "bar" }));
320+
}

0 commit comments

Comments
 (0)