diff --git a/example/simple.zig b/example/simple.zig index 55a4eaa..7fb8cd5 100644 --- a/example/simple.zig +++ b/example/simple.zig @@ -4,43 +4,38 @@ const cli = @import("zig-cli"); var gpa = std.heap.GeneralPurposeAllocator(.{}){}; const allocator = gpa.allocator(); -var abc: u16 = undefined; -var abc2: []const u8 = undefined; -var abc3: []u16 = undefined; -var wr = cli.AllocWrapper{ .alloc = allocator }; +var config = struct { + host: []const u8 = "localhost", + port: u16 = 3000, + base_value: f64 = undefined, + expose_metrics: bool = false, +}{}; -var ip_option = cli.Option{ - .long_name = "ip", - .help = "this is the IP address", - .short_alias = 'i', +var host_option = cli.Option{ + .long_name = "host", + .help = "host to listen on", + .short_alias = 'h', .value = cli.OptionValue{ .string = null }, .required = true, .value_name = "IP", }; -var int_option = cli.Option{ - .long_name = "int", - .help = "this is an int", +var port_option = cli.Option{ + .long_name = "port", + .help = "post to bind to", .value = cli.OptionValue{ .int = null }, - .value_ref = cli.valueRef(&abc), - // .value_ref2 = wr.sigleInt(&abc), }; -var bool_option = cli.Option{ - .long_name = "bool", - .short_alias = 'b', - .help = "this is a bool", +var expose_metrics_option = cli.Option{ + .long_name = "expose-metrics", + .short_alias = 'm', + .help = "if the metrics should be exposed", .value = cli.OptionValue{ .bool = false }, }; -var float_option = cli.Option{ - .long_name = "float", - .help = "this is a float", +var base_value_option = cli.Option{ + .long_name = "base-value", + .help = "base value", .value = cli.OptionValue{ .float = 0.34 }, }; -var name_option = cli.Option{ - .long_name = "long_name", - .help = "long_name help", - .value = cli.OptionValue{ .string = null }, -}; var app = &cli.App{ .name = "simple", .description = "This a simple CLI app\nEnjoy!", @@ -55,10 +50,10 @@ var app = &cli.App{ \\And this is line 3. , .options = &.{ - &ip_option, - &int_option, - &bool_option, - &float_option, + &host_option, + &port_option, + &expose_metrics_option, + &base_value_option, }, .subcommands = &.{ &cli.Command{ @@ -71,39 +66,20 @@ var app = &cli.App{ }; pub fn main() anyerror!void { - // this works - // var a: u16 = 3; - // var p = cli.IntParser(u16); - // var t = cli.singleValueRef(u16, &a, p, allocator); - // try t.put("56"); - // std.debug.print("a = {}\n", .{a}); - - // var ov = int_option.value_ref.?; - // std.log.info("hello", .{}); - // try ov.set("44"); - // std.log.debug("value: {}\n", .{abc}); - - // var ov = int_option; - // _ = ov; - // try ov.put("45"); - var ov = try wr.singleInt(&abc); - try ov.put("173"); - std.log.debug("value: {}\n", .{abc}); - - var a = try wr.string(&abc2); - try a.put("hello"); - std.log.debug("value: {s}\n", .{abc2}); + var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); + // defer arena.deinit(); + const al = arena.allocator(); - var a2 = try wr.multiInt(&abc3); - try a2.put("5"); - try a2.put("10"); - try a2.finalize(); - std.log.debug("value: {any}\n", .{abc3}); + var ctx = cli.Context.init(al); + host_option.value_ref = try ctx.valueRef(&config.host); + port_option.value_ref = try ctx.valueRef(&config.port); + expose_metrics_option.value_ref = try ctx.valueRef(&config.expose_metrics); + base_value_option.value_ref = try ctx.valueRef(&config.base_value); return cli.run(app, allocator); } fn run_sub2(args: []const []const u8) anyerror!void { - var ip = ip_option.value.string.?; - std.log.debug("running sub2: ip={s}, bool={any}, float={any} arg_count={any}", .{ ip, bool_option.value.bool, float_option.value.float, args.len }); + var ip = host_option.value.string.?; + std.log.debug("running sub2: ip={s}, bool={any}, float={any} arg_count={any}", .{ ip, expose_metrics_option.value.bool, base_value_option.value.float, args.len }); } diff --git a/src/command.zig b/src/command.zig index df7ef45..2ccda07 100644 --- a/src/command.zig +++ b/src/command.zig @@ -1,4 +1,7 @@ const std = @import("std"); +const vref = @import("./value_ref.zig"); +pub const ValueRef = vref.ValueRef; +pub const Context = vref.Context; pub const App = struct { name: []const u8, @@ -47,220 +50,12 @@ pub const OptionValue = union(enum) { string_list: ?[]const []const u8, }; -const Setter = *const fn (ptr: *anyopaque, value: []const u8) anyerror!void; - -pub const ValueRef = struct { - ptr: *anyopaque, - - vtable: *const VTable, - - const VTable = struct { - set: Setter, - }; - - // FIXME: a setter is not enough for multi-value options - /// Deprecated: use ValueRef2 - pub fn set(self: *ValueRef, value: []const u8) anyerror!void { - return self.vtable.set(self.ptr, value); - } -}; - -fn primitiveTypeVTable(comptime T: anytype) *const ValueRef.VTable { - const ptr_info = @typeInfo(T); - - std.debug.assert(ptr_info == .Pointer); - std.debug.assert(ptr_info.Pointer.size == .One); - const childT = ptr_info.Pointer.child; - const child_info = @typeInfo(childT); - - const setter = switch (child_info) { - .Int => a: { - const gen = struct { - fn setInt(ptr: *anyopaque, value: []const u8) anyerror!void { - var v = try std.fmt.parseInt(childT, value, 10); - const p = @as(*childT, @ptrCast(@alignCast(ptr))); - p.* = v; - } - }; - break :a gen.setInt; - }, - else => unreachable, - }; - - const vt = ValueRef.VTable{ - .set = setter, - }; - return &vt; -} - -pub fn valueRef(comptime ptr: anytype) ValueRef { - const ti = @TypeOf(ptr); - const vtable = primitiveTypeVTable(ti); - return .{ - .ptr = ptr, - .vtable = vtable, - }; -} - pub const Option = struct { long_name: []const u8, short_alias: ?u8 = null, help: []const u8, required: bool = false, value: OptionValue, - value_ref: ?ValueRef = null, - value_ref2: ?ValueRef2 = null, + value_ref: ?ValueRef = null, // TODO: remove ? value_name: []const u8 = "VALUE", }; - -pub fn mkOption(comptime ref: anytype, long_name: []const u8, help: []const u8) Option { - return .{ - .long_name = long_name, - .help = help, - .value_ref = valueRef(ref), - }; -} - -pub const ValueRef2 = struct { - impl_ptr: *anyopaque, - vtable: *const VTable, - - const Self = @This(); - - const VTable = struct { - put: *const fn (impl_ptr: *anyopaque, value: []const u8) anyerror!void, - - // finalize and destroy - finalize: *const fn (impl_ptr: *anyopaque) anyerror!void, - }; - - pub fn put(self: *Self, value: []const u8) anyerror!void { - return self.vtable.put(self.impl_ptr, value); - } - pub fn finalize(self: *Self) anyerror!void { - return self.vtable.finalize(self.impl_ptr); - } -}; - -// const Parser = *const fn (dest: *anyopaque, value: []const u8) anyerror!void; -fn Parser(comptime T: type) type { - // TODO: the parse function might need an allocator, e.g. to copy a string or allocate the destination type if it is a pointer. - return *const fn (dest: *T, value: []const u8) anyerror!void; -} - -pub fn IntParser(comptime T: type) Parser(T) { - return struct { - fn parser(dest: *T, value: []const u8) anyerror!void { - var v = try std.fmt.parseInt(T, value, 10); - dest.* = v; - } - }.parser; -} - -pub fn StringParser(comptime T: type) Parser(T) { - return struct { - fn parser(dest: *T, value: []const u8) anyerror!void { - dest.* = value; - } - }.parser; -} - -fn singleValueRef(comptime T: type, dest: *T, parser: Parser(T), alloc: std.mem.Allocator) !ValueRef2 { - const Impl = struct { - dest: *T, - parser: Parser(T), - alloc: std.mem.Allocator, - - const Self = @This(); - - fn put(ctx: *anyopaque, value: []const u8) anyerror!void { - const self: *Self = @ptrCast(@alignCast(ctx)); - try self.parser(self.dest, value); - } - fn finalize(ctx: *anyopaque) anyerror!void { - const self: *Self = @ptrCast(@alignCast(ctx)); - - // TODO: clarify what to do with copied strings??? - self.alloc.destroy(self); - } - }; - - // FIXME: this must be destroyed - const im = try alloc.create(Impl); - im.* = .{ - .dest = dest, - .parser = parser, - .alloc = alloc, - }; - - return ValueRef2{ .impl_ptr = im, .vtable = &.{ - .put = Impl.put, - .finalize = Impl.finalize, - } }; -} - -// TODO: implement multi-value reference, i.e. ref to a slice/array. -pub fn sliceRef(comptime T: type, dest: *[]T, parser: Parser(T), alloc: std.mem.Allocator) !ValueRef2 { - const List = std.ArrayList(T); - const Impl = struct { - dest: *[]T, - parser: Parser(T), - alloc: std.mem.Allocator, - list: List, - - const Self = @This(); - - fn put(ctx: *anyopaque, value: []const u8) anyerror!void { - const self: *Self = @ptrCast(@alignCast(ctx)); - var x: T = undefined; - try self.parser(&x, value); - try self.list.append(x); - } - fn finalize(ctx: *anyopaque) anyerror!void { - const self: *Self = @ptrCast(@alignCast(ctx)); - self.dest.* = try self.list.toOwnedSlice(); - self.alloc.destroy(self); - } - }; - - // FIXME: this must be destroyed - const im = try alloc.create(Impl); - im.* = .{ - .dest = dest, - .parser = parser, - .alloc = alloc, - .list = List.init(alloc), - }; - - return ValueRef2{ .impl_ptr = im, .vtable = &.{ - .put = Impl.put, - .finalize = Impl.finalize, - } }; -} - -pub const AllocWrapper = struct { - alloc: std.mem.Allocator, - - // TODO: make more generic to guess any value type (out of known), not only int - // TODO: consider parser registration for some type to allow to implement any possible parser - pub fn singleInt(self: *const AllocWrapper, dest: anytype) !ValueRef2 { - const ti = @typeInfo(@TypeOf(dest)); - const parser = IntParser(ti.Pointer.child); - return singleValueRef(ti.Pointer.child, dest, parser, self.alloc); - } - - pub fn multiInt(self: *const AllocWrapper, dest: anytype) !ValueRef2 { - // const ti = @typeInfo(@TypeOf(dest)); - const parser = IntParser(u16); - return sliceRef(u16, dest, parser, self.alloc); - } - - pub fn string(self: *const AllocWrapper, dest: anytype) !ValueRef2 { - const ti = @typeInfo(@TypeOf(dest)); - const parser = StringParser(ti.Pointer.child); - return singleValueRef(ti.Pointer.child, dest, parser, self.alloc); - } - - // TODO: consider arena allocator - // TODO: implement deinit -}; diff --git a/src/value_parser.zig b/src/value_parser.zig new file mode 100644 index 0000000..c030179 --- /dev/null +++ b/src/value_parser.zig @@ -0,0 +1,53 @@ +const std = @import("std"); + +// const Parser = *const fn (dest: *anyopaque, value: []const u8) anyerror!void; +pub fn ValueParser(comptime T: type) type { + // TODO: the parse function might need an allocator, e.g. to copy a string or allocate the destination type if it is a pointer. + return *const fn (dest: *T, value: []const u8) anyerror!void; +} + +pub fn get(comptime T: type) ValueParser(T) { + return switch (@typeInfo(T)) { + .Int => intParser(T), + .Float => floatParser(T), + .Bool => boolParser(T), + .Pointer => |pinfo| { + if (pinfo.size == .Slice and pinfo.child == u8) { + stringParser(T); + } + }, + else => @compileError("unsupported value type"), + }; +} + +fn intParser(comptime T: type) ValueParser(T) { + return struct { + fn parser(dest: *T, value: []const u8) anyerror!void { + dest.* = try std.fmt.parseInt(T, value, 10); + } + }.parser; +} + +fn floatParser(comptime T: type) ValueParser(T) { + return struct { + fn parser(dest: *T, value: []const u8) anyerror!void { + dest.* = try std.fmt.parseFloat(T, value); + } + }.parser; +} + +fn boolParser(comptime T: type) ValueParser(T) { + return struct { + fn parser(dest: *T, value: []const u8) anyerror!void { + dest.* = std.mem.eql(u8, value, "true"); + } + }.parser; +} + +fn stringParser(comptime T: type) ValueParser(T) { + return struct { + fn parser(dest: *T, value: []const u8) anyerror!void { + dest.* = value; + } + }.parser; +} diff --git a/src/value_ref.zig b/src/value_ref.zig new file mode 100644 index 0000000..626da99 --- /dev/null +++ b/src/value_ref.zig @@ -0,0 +1,132 @@ +const std = @import("std"); +const command = @import("./command.zig"); +const vp = @import("./value_parser.zig"); + +pub const ValueRef = struct { + impl_ptr: *anyopaque, + vtable: *const VTable, + + const Self = @This(); + + const VTable = struct { + put: *const fn (impl_ptr: *anyopaque, value: []const u8) anyerror!void, + + /// finalize and destroy + finalize: *const fn (impl_ptr: *anyopaque) anyerror!void, + }; + + pub fn put(self: *Self, value: []const u8) anyerror!void { + return self.vtable.put(self.impl_ptr, value); + } + pub fn finalize(self: *Self) anyerror!void { + return self.vtable.finalize(self.impl_ptr); + } +}; + +// TODO: can ValueRef be an enum???? + +const AllocError = std.mem.Allocator.Error; +pub const Error = AllocError; // | error{NotImplemented}; + +pub const Context = struct { + alloc: std.mem.Allocator, + + pub fn init(a: std.mem.Allocator) Context { + return .{ + .alloc = a, + }; + } + + fn singleValueRef(ctx: *Context, comptime T: type, dest: *T, parser: vp.ValueParser(T)) AllocError!ValueRef { + const Impl = struct { + dest: *T, + parser: vp.ValueParser(T), + alloc: std.mem.Allocator, + + const Self = @This(); + + fn put(ptr: *anyopaque, value: []const u8) anyerror!void { + const self: *Self = @ptrCast(@alignCast(ptr)); + try self.parser(self.dest, value); + } + fn finalize(ptr: *anyopaque) anyerror!void { + const self: *Self = @ptrCast(@alignCast(ptr)); + + // TODO: clarify what to do with copied strings??? + self.alloc.destroy(self); + } + }; + + // FIXME: this must be destroyed + const im = try ctx.alloc.create(Impl); + im.* = .{ + .dest = dest, + .parser = parser, + .alloc = ctx.alloc, + }; + + return ValueRef{ .impl_ptr = im, .vtable = &.{ + .put = Impl.put, + .finalize = Impl.finalize, + } }; + } + + pub fn sliceRef(ctx: *Context, comptime T: type, dest: *[]const T, parser: vp.ValueParser(T)) AllocError!ValueRef { + const List = std.ArrayList(T); + const Impl = struct { + dest: *[]const T, + parser: vp.ValueParser(T), + list: List, + alloc2: std.mem.Allocator, + + const Self = @This(); + + fn put(ptr: *anyopaque, value: []const u8) anyerror!void { + const self: *Self = @ptrCast(@alignCast(ptr)); + var x: T = undefined; + try self.parser(&x, value); + try self.list.append(x); + } + fn finalize(ptr: *anyopaque) anyerror!void { + const self: *Self = @ptrCast(@alignCast(ptr)); + self.dest.* = try self.list.toOwnedSlice(); + self.alloc2.destroy(self); + } + }; + + // FIXME: this must be destroyed + const im = try ctx.alloc.create(Impl); + im.* = .{ + .dest = dest, + .parser = parser, + .list = List.init(ctx.alloc), + .alloc2 = ctx.alloc, + }; + + return ValueRef{ .impl_ptr = im, .vtable = &.{ + .put = Impl.put, + .finalize = Impl.finalize, + } }; + } + + pub fn valueRef(ctx: *Context, comptime dest: anytype) Error!ValueRef { + const ti = @typeInfo(@TypeOf(dest)); + const t = ti.Pointer.child; + + switch (@typeInfo(t)) { + .Pointer => |pinfo| { + switch (pinfo.size) { + .Slice => { + const p = vp.get(pinfo.child); + return ctx.sliceRef(pinfo.child, dest, p); + }, + else => @compileError("unsupported value type"), + } + }, + else => { + const p = vp.get(t); + return ctx.singleValueRef(t, dest, p); + }, + } + } +};