summaryrefslogtreecommitdiff
path: root/src/extended.zig
blob: 09e91dd3c138814729d96b0e79afc93dac267292 (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
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
pub const core = @import("core.zig");

const builtin = @import("builtin");
const std = @import("std");

const mem = std.mem;
const fmt = std.fmt;
const debug = std.debug;
const io = std.io;

const assert = debug.assert;

pub const Param = struct {
    field: []const u8,
    names: core.Names,
    settings: Settings,
    kind: Kind,

    required: bool,
    position: ?usize,

    pub fn flag(field: []const u8, names: *const core.Names) Param {
        return init(
            field,
            names,
            Kind.Flag,
        );
    }

    pub fn option(
        field: []const u8,
        names: *const core.Names,
        comptime parser: *const Parser,
    ) Param {
        return init(
            field,
            names,
            Kind{ .Option = parser.* },
        );
    }

    pub fn subcommand(
        field: []const u8,
        names: *const core.Names,
        comptime command: *const Command,
    ) Param {
        return init(
            field,
            names,
            Kind{ .Subcommand = command.* },
        );
    }

    pub fn init(field: []const u8, names: *const core.Names, kind: *const Kind) Param {
        return Param{
            .field = field,
            .names = names.*,
            .kind = kind.*,
            .required = false,
            .position = null,
        };
    }

    pub const Kind = union(enum) {
        Flag,
        Option: Parser,
        Subcommand: Command,
    };
};

const Opaque = @OpaqueType();
pub const Command = struct {
    params: []const Param,

    Result: type,
    default: *const Opaque,

    pub fn init(comptime Result: type, default: *const Result, params: []const Param) Command {
        return Command{
            .params = params,
            .Result = Result,
            .default = @ptrCast(*const Opaque, default),
        };
    }
};

pub const Parser = struct {
    const UnsafeFunction = *const void;

    FieldType: type,
    Errors: type,
    func: UnsafeFunction,

    pub fn init(comptime FieldType: type, comptime Errors: type, func: ParseFunc(FieldType, Errors)) Parser {
        return Parser{
            .FieldType = FieldType,
            .Errors = Errors,
            .func = @ptrCast(UnsafeFunction, func),
        };
    }

    fn parse(comptime parser: Parser, field_ptr: *parser.FieldType, arg: []const u8) parser.Errors!void {
        return @ptrCast(ParseFunc(parser.FieldType, parser.Errors), parser.func)(field_ptr, arg);
    }

    fn ParseFunc(comptime FieldType: type, comptime Errors: type) type {
        return fn (*FieldType, []const u8) Errors!void;
    }

    pub fn int(comptime Int: type, comptime radix: u8) Parser {
        const func = struct {
            fn i(field_ptr: *Int, arg: []const u8) !void {
                field_ptr.* = try fmt.parseInt(Int, arg, radix);
            }
        }.i;
        return Parser.init(Int, @typeOf(func).ReturnType.ErrorSet, func);
    }

    const string = Parser.init([]const u8, error{}, struct {
        fn s(field_ptr: *[]const u8, arg: []const u8) (error{}!void) {
            field_ptr.* = arg;
        }
    }.s);
};

pub fn Clap(comptime Result: type) type {
    return struct {
        const Self = this;

        default: Result,
        params: []const Param,

        pub fn parse(
            comptime clap: *const Self,
            comptime Error: type,
            iter: *core.ArgIterator(Error),
        ) !Result {
            // We initialize the core.Clap without any params, and fill them out in parseHelper.
            var c = core.Clap(usize, Error).init([]core.Param(usize){}, iter);

            const top_level_command = comptime Command.init(Result, &clap.default, clap.params);
            return try parseHelper(&top_level_command, Error, &c);
        }

        fn parseHelper(
            comptime command: *const Command,
            comptime Error: type,
            clap: *core.Clap(usize, Error),
        ) !command.Result {
            var result = @ptrCast(*const command.Result, command.default).*;

            var handled = comptime blk: {
                var res: [command.params.len]bool = undefined;
                for (command.params) |p, i| {
                    res[i] = !p.settings.required;
                }

                break :blk res;
            };

            // We replace the current clap with the commands parameters, so that we preserve the that
            // claps state. This is important, as core.Clap could be in a Chaining state, and
            // constructing a new core.Clap would skip the last chaining arguments.
            clap.params = comptime blk: {
                var res: [command.params.len]core.Param(usize) = undefined;

                for (command.params) |p, i| {
                    const id = i;
                    res[id] = core.Param(usize){
                        .id = id,
                        .takes_value = p.kind == Param.Kind.Option,
                        .names = p.names,
                    };
                }

                break :blk res;
            };

            var pos: usize = 0;

            arg_loop: while (try clap.next()) |arg| : (pos += 1) {
                inline for (command.params) |param, i| {
                    if (arg.param.id == i and (param.settings.position orelse pos) == pos) {
                        handled[i] = true;

                        switch (param.kind) {
                            Param.Kind.Flag => {
                                getFieldPtr(&result, param.field).* = true;
                            },
                            Param.Kind.Option => |parser| {
                                try parser.parse(getFieldPtr(&result, param.field), arg.value.?);
                            },
                            Param.Kind.Subcommand => |sub_command| {
                                getFieldPtr(&result, param.field).* = try sub_command.parseHelper(Error, clap);

                                // After parsing a subcommand, there should be no arguments left.
                                break :arg_loop;
                            },
                        }
                        continue :arg_loop;
                    }
                }

                return error.InvalidArgument;
            }

            for (handled) |h| {
                if (!h)
                    return error.ParamNotHandled;
            }

            return result;
        }

        fn GetFieldPtrReturn(comptime Struct: type, comptime field: []const u8) type {
            var inst: Struct = undefined;
            const dot_index = comptime mem.indexOfScalar(u8, field, '.') orelse {
                return @typeOf(&@field(inst, field));
            };

            return GetFieldPtrReturn(@typeOf(@field(inst, field[0..dot_index])), field[dot_index + 1 ..]);
        }

        fn getFieldPtr(curr: var, comptime field: []const u8) GetFieldPtrReturn(@typeOf(curr).Child, field) {
            const dot_index = comptime mem.indexOfScalar(u8, field, '.') orelse {
                return &@field(curr, field);
            };

            return getFieldPtr(&@field(curr, field[0..dot_index]), field[dot_index + 1 ..]);
        }
    };
}