aboutsummaryrefslogtreecommitdiff
path: root/common/flags.zig
blob: 1e34ce1e6bf61bedf4e3b99156840d9325aa9d6f (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
// Zero allocation argument parsing for unix-like systems.
// Released under the Zero Clause BSD (0BSD) license:
//
// Copyright 2023 Isaac Freund
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

const std = @import("std");
const mem = std.mem;

pub const Flag = struct {
    name: []const u8,
    kind: enum { boolean, arg },
};

pub fn parser(comptime Arg: type, comptime flags: []const Flag) type {
    switch (Arg) {
        // TODO consider allowing []const u8
        [:0]const u8, [*:0]const u8 => {}, // ok
        else => @compileError("invalid argument type: " ++ @typeName(Arg)),
    }
    return struct {
        pub const Result = struct {
            /// Remaining args after the recognized flags
            args: []const Arg,
            /// Data obtained from parsed flags
            flags: Flags,

            pub const Flags = flags_type: {
                var fields: []const std.builtin.Type.StructField = &.{};
                inline for (flags) |flag| {
                    const field: std.builtin.Type.StructField = switch (flag.kind) {
                        .boolean => .{
                            .name = flag.name,
                            .field_type = bool,
                            .default_value = &false,
                            .is_comptime = false,
                            .alignment = @alignOf(bool),
                        },
                        .arg => .{
                            .name = flag.name,
                            .field_type = ?[:0]const u8,
                            .default_value = &@as(?[:0]const u8, null),
                            .is_comptime = false,
                            .alignment = @alignOf(?[:0]const u8),
                        },
                    };
                    fields = fields ++ [_]std.builtin.Type.StructField{field};
                }
                break :flags_type @Type(.{ .Struct = .{
                    .layout = .Auto,
                    .fields = fields,
                    .decls = &.{},
                    .is_tuple = false,
                } });
            };
        };

        pub fn parse(args: []const Arg) !Result {
            var result_flags: Result.Flags = .{};

            var i: usize = 0;
            outer: while (i < args.len) : (i += 1) {
                inline for (flags) |flag| {
                    if (mem.eql(u8, "-" ++ flag.name, mem.span(args[i]))) {
                        switch (flag.kind) {
                            .boolean => @field(result_flags, flag.name) = true,
                            .arg => {
                                i += 1;
                                if (i == args.len) {
                                    std.log.err("option '-" ++ flag.name ++
                                        "' requires an argument but none was provided!", .{});
                                    return error.MissingFlagArgument;
                                }
                                @field(result_flags, flag.name) = mem.span(args[i]);
                            },
                        }
                        continue :outer;
                    }
                }
                break;
            }

            return Result{
                .args = args[i..],
                .flags = result_flags,
            };
        }
    };
}