├── .gitignore ├── LICENSE ├── README.md ├── build.zig ├── example.zig └── getopt.zig /.gitignore: -------------------------------------------------------------------------------- 1 | /zig-cache/ 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2021 Dmitri Goutnik 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any 4 | purpose with or without fee is hereby granted. 5 | 6 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 7 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 8 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 9 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 10 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 11 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 12 | PERFORMANCE OF THIS SOFTWARE. 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Minimal POSIX getopt(3) implementation in Zig 2 | 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. 4 | 5 | ## Example 6 | 7 | ```zig 8 | const std = @import("std"); 9 | const debug = std.debug; 10 | const getopt = @import("getopt.zig"); 11 | 12 | pub fn main() void { 13 | var arg: []const u8 = undefined; 14 | var verbose: bool = false; 15 | 16 | var opts = getopt.getopt("a:vh"); 17 | 18 | while (opts.next()) |maybe_opt| { 19 | if (maybe_opt) |opt| { 20 | switch (opt.opt) { 21 | 'a' => { 22 | arg = opt.arg.?; 23 | debug.print("arg = {s}\n", .{arg}); 24 | }, 25 | 'v' => { 26 | verbose = true; 27 | debug.print("verbose = {}\n", .{verbose}); 28 | }, 29 | 'h' => debug.print( 30 | \\usage: example [-a arg] [-hv] 31 | \\ 32 | , .{}), 33 | else => unreachable, 34 | } 35 | } else break; 36 | } else |err| { 37 | switch (err) { 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}), 40 | } 41 | } 42 | 43 | debug.print("remaining args: {?s}\n", .{opts.args()}); 44 | } 45 | ``` 46 | 47 | ``` 48 | $ zig run example.zig -- -hv -a42 foo bar 49 | usage: example [-a arg] [-hv] 50 | verbose = true 51 | arg = 42 52 | remaining args: { foo, bar } 53 | ``` 54 | -------------------------------------------------------------------------------- /build.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | pub fn build(b: *std.build.Builder) void { 4 | const target = b.standardTargetOptions(.{}); 5 | const optimize = b.standardOptimizeOption(.{}); 6 | 7 | _ = b.addModule("getopt", .{ 8 | .source_file = .{ 9 | .path = "getopt.zig", 10 | }, 11 | }); 12 | 13 | const tests = b.addTest(.{ 14 | .root_source_file = .{ .path = "getopt.zig" }, 15 | .target = target, 16 | .optimize = optimize, 17 | }); 18 | const run_tests = b.addRunArtifact(tests); 19 | 20 | const test_step = b.step("test", "Run tests"); 21 | test_step.dependOn(&run_tests.step); 22 | } 23 | -------------------------------------------------------------------------------- /example.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const debug = std.debug; 3 | const getopt = @import("getopt.zig"); 4 | 5 | pub fn main() void { 6 | var arg: []const u8 = undefined; 7 | var verbose: bool = false; 8 | 9 | var opts = getopt.getopt("a:vh"); 10 | 11 | while (opts.next()) |maybe_opt| { 12 | if (maybe_opt) |opt| { 13 | switch (opt.opt) { 14 | 'a' => { 15 | arg = opt.arg.?; 16 | debug.print("arg = {s}\n", .{arg}); 17 | }, 18 | 'v' => { 19 | verbose = true; 20 | debug.print("verbose = {}\n", .{verbose}); 21 | }, 22 | 'h' => debug.print( 23 | \\usage: example [-a arg] [-hv] 24 | \\ 25 | , .{}), 26 | else => unreachable, 27 | } 28 | } else break; 29 | } else |err| { 30 | switch (err) { 31 | getopt.Error.InvalidOption => debug.print("invalid option: {c}\n", .{opts.optopt}), 32 | getopt.Error.MissingArgument => debug.print("option requires an argument: {c}\n", .{opts.optopt}), 33 | } 34 | } 35 | 36 | debug.print("remaining args: {?s}\n", .{opts.args()}); 37 | } 38 | -------------------------------------------------------------------------------- /getopt.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const ascii = std.ascii; 3 | const mem = std.mem; 4 | const os = std.os; 5 | const expect = std.testing.expect; 6 | 7 | /// Parsed option struct. 8 | pub const Option = struct { 9 | /// Option character. 10 | opt: u8, 11 | 12 | /// Option argument, if any. 13 | arg: ?[]const u8 = null, 14 | }; 15 | 16 | pub const Error = error{ InvalidOption, MissingArgument }; 17 | 18 | pub const OptionsIterator = struct { 19 | argv: [][*:0]const u8, 20 | opts: []const u8, 21 | 22 | /// Index of the current element of the argv vector. 23 | optind: usize = 1, 24 | 25 | optpos: usize = 1, 26 | 27 | /// Current option character. 28 | optopt: u8 = undefined, 29 | 30 | pub fn next(self: *OptionsIterator) Error!?Option { 31 | if (self.optind == self.argv.len) 32 | return null; 33 | 34 | const arg = self.argv[self.optind]; 35 | 36 | if (mem.eql(u8, mem.span(arg), "--")) { 37 | self.optind += 1; 38 | return null; 39 | } 40 | 41 | if (arg[0] != '-' or !ascii.isAlphanumeric(arg[1])) 42 | return null; 43 | 44 | self.optopt = arg[self.optpos]; 45 | 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] == ':') { 49 | if (arg[self.optpos + 1] != 0) { 50 | const res = Option{ 51 | .opt = self.optopt, 52 | .arg = mem.span(arg + self.optpos + 1), 53 | }; 54 | self.optind += 1; 55 | self.optpos = 1; 56 | return res; 57 | } else if (self.optind + 1 < self.argv.len) { 58 | const res = Option{ 59 | .opt = self.optopt, 60 | .arg = mem.span(self.argv[self.optind + 1]), 61 | }; 62 | self.optind += 2; 63 | self.optpos = 1; 64 | return res; 65 | } else return Error.MissingArgument; 66 | } else { 67 | self.optpos += 1; 68 | if (arg[self.optpos] == 0) { 69 | self.optind += 1; 70 | self.optpos = 1; 71 | } 72 | return Option{ .opt = self.optopt }; 73 | } 74 | } else return Error.InvalidOption; 75 | } 76 | 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; 83 | } 84 | }; 85 | 86 | fn getoptArgv(argv: [][*:0]const u8, optstring: []const u8) OptionsIterator { 87 | return OptionsIterator{ 88 | .argv = argv, 89 | .opts = optstring, 90 | }; 91 | } 92 | 93 | /// Parse os.argv according to the optstring. 94 | pub fn getopt(optstring: []const u8) OptionsIterator { 95 | // https://github.com/ziglang/zig/issues/8808 96 | const argv: [][*:0]const u8 = os.argv; 97 | return getoptArgv(argv, optstring); 98 | } 99 | 100 | test "no args separate" { 101 | var argv = [_][*:0]const u8{ 102 | "getopt", 103 | "-a", 104 | "-b", 105 | }; 106 | 107 | const expected = [_]Option{ 108 | .{ .opt = 'a' }, 109 | .{ .opt = 'b' }, 110 | }; 111 | 112 | var opts = getoptArgv(&argv, "ab"); 113 | 114 | var i: usize = 0; 115 | while (try opts.next()) |opt| : (i += 1) { 116 | try expect(opt.opt == expected[i].opt); 117 | if (opt.arg != null and expected[i].arg != null) { 118 | try expect(mem.eql(u8, opt.arg.?, expected[i].arg.?)); 119 | } else { 120 | try expect(opt.arg == null and expected[i].arg == null); 121 | } 122 | } 123 | 124 | try expect(opts.args() == null); 125 | } 126 | 127 | test "no args joined" { 128 | var argv = [_][*:0]const u8{ 129 | "getopt", 130 | "-abc", 131 | }; 132 | 133 | const expected = [_]Option{ 134 | .{ .opt = 'a' }, 135 | .{ .opt = 'b' }, 136 | .{ .opt = 'c' }, 137 | }; 138 | 139 | var opts = getoptArgv(&argv, "abc"); 140 | 141 | var i: usize = 0; 142 | while (try opts.next()) |opt| : (i += 1) { 143 | try expect(opt.opt == expected[i].opt); 144 | if (opt.arg != null and expected[i].arg != null) { 145 | try expect(mem.eql(u8, opt.arg.?, expected[i].arg.?)); 146 | } else { 147 | try expect(opt.arg == null and expected[i].arg == null); 148 | } 149 | } 150 | } 151 | 152 | test "with args separate" { 153 | var argv = [_][*:0]const u8{ 154 | "getopt", 155 | "-a10", 156 | "-b", 157 | "-c", 158 | "42", 159 | }; 160 | 161 | const expected = [_]Option{ 162 | .{ .opt = 'a', .arg = "10" }, 163 | .{ .opt = 'b' }, 164 | .{ .opt = 'c', .arg = "42" }, 165 | }; 166 | 167 | var opts = getoptArgv(&argv, "a:bc:"); 168 | 169 | var i: usize = 0; 170 | while (try opts.next()) |opt| : (i += 1) { 171 | try expect(opt.opt == expected[i].opt); 172 | if (opt.arg != null and expected[i].arg != null) { 173 | try expect(mem.eql(u8, opt.arg.?, expected[i].arg.?)); 174 | } else { 175 | try expect(opt.arg == null and expected[i].arg == null); 176 | } 177 | } 178 | } 179 | 180 | test "with args joined" { 181 | var argv = [_][*:0]const u8{ 182 | "getopt", 183 | "-a10", 184 | "-bc", 185 | "42", 186 | }; 187 | 188 | const expected = [_]Option{ 189 | .{ .opt = 'a', .arg = "10" }, 190 | .{ .opt = 'b' }, 191 | .{ .opt = 'c', .arg = "42" }, 192 | }; 193 | 194 | var opts = getoptArgv(&argv, "a:bc:"); 195 | 196 | var i: usize = 0; 197 | while (try opts.next()) |opt| : (i += 1) { 198 | try expect(opt.opt == expected[i].opt); 199 | if (opt.arg != null and expected[i].arg != null) { 200 | try expect(mem.eql(u8, opt.arg.?, expected[i].arg.?)); 201 | } else { 202 | try expect(opt.arg == null and expected[i].arg == null); 203 | } 204 | } 205 | } 206 | 207 | test "invalid option" { 208 | var argv = [_][*:0]const u8{ 209 | "getopt", 210 | "-az", 211 | }; 212 | 213 | var opts = getoptArgv(&argv, "a"); 214 | 215 | // -a is ok 216 | try expect((try opts.next()).?.opt == 'a'); 217 | 218 | const maybe_opt = opts.next(); 219 | if (maybe_opt) |_| { 220 | unreachable; 221 | } else |err| { 222 | try expect(err == Error.InvalidOption); 223 | try expect(opts.optopt == 'z'); 224 | } 225 | } 226 | 227 | test "missing argument" { 228 | var argv = [_][*:0]const u8{ 229 | "getopt", 230 | "-az", 231 | }; 232 | 233 | var opts = getoptArgv(&argv, "az:"); 234 | 235 | // -a is ok 236 | try expect((try opts.next()).?.opt == 'a'); 237 | 238 | const maybe_opt = opts.next(); 239 | if (maybe_opt) |_| { 240 | unreachable; 241 | } else |err| { 242 | try expect(err == Error.MissingArgument); 243 | try expect(opts.optopt == 'z'); 244 | } 245 | } 246 | 247 | test "positional args" { 248 | var argv = [_][*:0]const u8{ 249 | "getopt", 250 | "-abc10", 251 | "-d", 252 | "foo", 253 | "bar", 254 | }; 255 | 256 | const expected = [_]Option{ 257 | .{ .opt = 'a' }, 258 | .{ .opt = 'b' }, 259 | .{ .opt = 'c', .arg = "10" }, 260 | .{ .opt = 'd' }, 261 | }; 262 | 263 | var opts = getoptArgv(&argv, "abc:d"); 264 | 265 | var i: usize = 0; 266 | while (try opts.next()) |opt| : (i += 1) { 267 | try expect(opt.opt == expected[i].opt); 268 | if (opt.arg != null and expected[i].arg != null) { 269 | try expect(mem.eql(u8, opt.arg.?, expected[i].arg.?)); 270 | } else { 271 | try expect(opt.arg == null and expected[i].arg == null); 272 | } 273 | } 274 | 275 | try expect(mem.eql([*:0]const u8, opts.args().?, &[_][*:0]const u8{ "foo", "bar" })); 276 | } 277 | 278 | test "positional args with separator" { 279 | var argv = [_][*:0]const u8{ 280 | "getopt", 281 | "-ab", 282 | "--", 283 | "foo", 284 | "bar", 285 | }; 286 | 287 | const expected = [_]Option{ 288 | .{ .opt = 'a' }, 289 | .{ .opt = 'b' }, 290 | }; 291 | 292 | var opts = getoptArgv(&argv, "ab"); 293 | 294 | var i: usize = 0; 295 | while (try opts.next()) |opt| : (i += 1) { 296 | try expect(opt.opt == expected[i].opt); 297 | if (opt.arg != null and expected[i].arg != null) { 298 | try expect(mem.eql(u8, opt.arg.?, expected[i].arg.?)); 299 | } else { 300 | try expect(opt.arg == null and expected[i].arg == null); 301 | } 302 | } 303 | 304 | try expect(mem.eql([*:0]const u8, opts.args().?, &[_][*:0]const u8{ "foo", "bar" })); 305 | } 306 | --------------------------------------------------------------------------------