diff --git a/src/args/arg.zig b/src/args/arg.zig index 7181247..d0ba809 100644 --- a/src/args/arg.zig +++ b/src/args/arg.zig @@ -1,5 +1,7 @@ const std = @import("std"); +const log = std.log.scoped(.args); + pub const Arg = union(enum(u2)) { Flag: struct { name: []const u8, @@ -75,7 +77,7 @@ pub const Arg = union(enum(u2)) { switch (flag) { .Flag => |f| { if (f.name.len == 0) { - std.debug.print("failed to parse flag: {s}\n", .{arg}); + log.err("failed to parse flag: {s}\n", .{arg}); return error.InvalidFlag; } }, diff --git a/src/args/args.zig b/src/args/args.zig index 039972b..ee074c5 100644 --- a/src/args/args.zig +++ b/src/args/args.zig @@ -3,10 +3,11 @@ const std = @import("std"); const Allocator = std.mem.Allocator; pub const parsers = @import("./parsers/parsers.zig"); -pub const validators = @import("./validators/validators.zig"); const Arg = @import("./arg.zig").Arg; +const log = std.log.scoped(.args); + /// Metadata for a field that is parseable by the argument parser. pub const Extra = union(enum(u2)) { Positional, @@ -31,13 +32,6 @@ pub fn Marker(comptime T: type) type { /// /// However, it is recommended to use `lys.args.parsers` for common types. parse: ?parsers.ParseSignature(T) = null, - /// Optional function that validates the value of the field. - /// If this is null, no validation will take place. - /// - /// This function can be any function that takes a `[]const u8` and returns `anyerror!void`. - /// - /// However, it is recommended to use `lys.args.validators` for common types. - validate: ?validators.ValidateSignature = null, }; } @@ -127,8 +121,10 @@ fn nextPositional(flags: []Arg) ?*Arg { /// Does the actual work of initializing T from flags. fn initFromParsed(comptime T: type, allocator: Allocator, flags: []Arg) !T { comptime { - std.debug.assert(@hasField(T, "allocator")); - std.debug.assert(@hasDecl(T, "deinit")); + if (!@hasField(T, "allocator")) + @compileError("T must have an allocator"); + if (!@hasDecl(T, "deinit")) + @compileError("T must have a deinit function"); } var result = T{ @@ -154,7 +150,7 @@ fn initFromParsed(comptime T: type, allocator: Allocator, flags: []Arg) !T { switch (extra) { .Flag => |f| { if (!(f.takesValue or f.toggle)) { - std.debug.print("error: invalid flag `{s}`: is not a toggle and doesn't take a value\n", .{f.name}); + log.err("invalid flag `{s}`: is not a toggle and doesn't take a value\n", .{f.name}); return error.InvalidFlag; } @@ -167,7 +163,7 @@ fn initFromParsed(comptime T: type, allocator: Allocator, flags: []Arg) !T { fie.*.value = true; comptime continue; } else { - std.debug.print("error: invalid switch {s}: expected bool, found {s}\n", .{ + log.err("invalid switch `{s}`: expected T == bool, found: {s}\n", .{ f.name, @typeName(@TypeOf(fie.*.value)), }); @@ -182,25 +178,36 @@ fn initFromParsed(comptime T: type, allocator: Allocator, flags: []Arg) !T { comptime continue; } else { if (fie.*.parse) |parse_fn| { - fie.*.value = try parse_fn(value); + fie.*.value = parse_fn(value) catch |err| { + log.err("could not parse flag `{s}`: `{s}`\n", .{ + field.name, + @errorName(err), + }); + log.err("- tried to parse: {s}\n", .{value}); + log.err("- expected type: {s}\n", .{@typeName(@TypeOf(fie.*.value))}); + return error.InvalidFlag; + }; comptime continue; } else { - std.debug.print("error: flag `{s}` expected a value, but no parser was provided\n", .{f.name}); + log.err("flag `{s}` expected a value, but no parser was provided for type `{s}`\n", .{ + f.name, + @typeName(@TypeOf(fie.*.value)), + }); return error.NoValueForFlag; } unreachable; } } else { - std.debug.print("error: flag `{s}` expected a value, but none was provided\n", .{f.name}); + log.err("flag `{s}` expected a value, but none was provided\n", .{f.name}); return error.NoValueForFlag; } } - std.debug.print("error: invalid flag `{s}`: is not a toggle and doesn't take a value\n", .{f.name}); + log.err("invalid flag `{s}`: is not a toggle and doesn't take a value\n", .{f.name}); return error.InvalidFlag; } else { - std.debug.print("error: invalid flag `{s}`: could not find flag\n", .{f.name}); + log.err("invalid flag `{s}`: could not find flag\n", .{f.name}); return error.InvalidFlag; } }, @@ -213,7 +220,7 @@ fn initFromParsed(comptime T: type, allocator: Allocator, flags: []Arg) !T { switch (flag.*) { .Flag => |f| { - std.debug.print("TODO: figure out what to do with remainder flag {s} (value: {?s})\n", .{ + log.warn("TODO: figure out what to do with remainder flag {s} (value: {?s})\n", .{ f.name, f.value, }); @@ -228,7 +235,7 @@ fn initFromParsed(comptime T: type, allocator: Allocator, flags: []Arg) !T { if (@TypeOf(fie.*.value) == std.ArrayList([]const u8)) { fie.*.value = not_consumed; } else { - std.debug.print("error: invalid remainder {s}: expected `std.ArrayList([] const u8)`, got `{s}`\n", .{ + log.err("invalid remainder field `{s}`: expected T == `std.ArrayList([] const u8)`, got T == `{s}`\n", .{ field.name, @typeName(@TypeOf(fie.*.value)), }); @@ -244,20 +251,20 @@ fn initFromParsed(comptime T: type, allocator: Allocator, flags: []Arg) !T { fie.*.value = try result.allocator.dupe(u8, flag.*.Positional.value); } else if (fie.*.parse) |parse_fn| { fie.*.value = parse_fn(flag.*.Positional.value) catch |err| { - std.debug.print("error: could not parse positional argument for {s}: {s}\n", .{ + log.err("could not parse positional argument for `{s}`: `{s}`\n", .{ field.name, @errorName(err), }); - std.debug.print("- tried to parse: {s}\n", .{flag.*.Positional.value}); - std.debug.print("- expected type: {s}\n", .{@typeName(@TypeOf(fie.*.value))}); + log.err("- tried to parse: {s}\n", .{flag.*.Positional.value}); + log.err("- expected type: {s}\n", .{@typeName(@TypeOf(fie.*.value))}); return error.InvalidPositional; }; } else { - std.debug.print("error: could not parse positional argument for {s}\n", .{field.name}); + log.err("could not parse positional argument for `{s}`: no parser provided\n", .{field.name}); return error.InvalidPositional; } } else { - std.debug.print("error: could not find positional argument for {s}\n", .{field.name}); + log.err("could not find positional argument for `{s}`\n", .{field.name}); return error.NoArgumentFound; } }, diff --git a/src/args/validators/number.zig b/src/args/validators/number.zig deleted file mode 100644 index e69de29..0000000 diff --git a/src/args/validators/string.zig b/src/args/validators/string.zig deleted file mode 100644 index c48bbfb..0000000 --- a/src/args/validators/string.zig +++ /dev/null @@ -1,32 +0,0 @@ -const std = @import("std"); -const t = std.testing; - -pub fn notEmpty(value: []const u8) anyerror!bool { - if (value.len == 0) { - return false; - } - - return true; -} - -test "not empty" { - try t.expect(try notEmpty("") == false); - try t.expect(try notEmpty("a")); -} - -pub fn notWhiteSpace(value: []const u8) anyerror!bool { - if (std.mem.trim(u8, value, " \t\n\r").len == 0) { - return false; - } - - return true; -} - -test "not white space" { - try t.expect(try notWhiteSpace("") == false); - try t.expect(try notWhiteSpace("\n\r\t ") == false); - try t.expect(try notWhiteSpace("a")); - try t.expect(try notWhiteSpace(" a")); - try t.expect(try notWhiteSpace("\ta")); - try t.expect(try notWhiteSpace("\n\r\t a")); -} diff --git a/src/args/validators/validators.zig b/src/args/validators/validators.zig deleted file mode 100644 index a34cf11..0000000 --- a/src/args/validators/validators.zig +++ /dev/null @@ -1,37 +0,0 @@ -const std = @import("std"); - -/// A function that validates a string value. -/// It should return an error if the value is unparseable. -/// Or `true` if the value is valid. -/// Or `false` if the value is invalid. -pub const ValidateSignature = *const fn (value: []const u8) anyerror!bool; - -pub const string = @import("./string.zig"); -pub const number = @import("./number.zig"); - -comptime { - const builtin = @import("builtin"); - - if (builtin.is_test) { - std.mem.doNotOptimizeAway(string); - std.mem.doNotOptimizeAway(number); - } -} - -const this = @This(); -test "valid validator signatures" { - const t = std.testing; - - const decls = @typeInfo(this).Struct.decls; - - inline for (decls) |decl| { - const field = @field(this, decl.name); - if (comptime std.mem.startsWith(u8, @typeName(field), "*")) { - comptime continue; // skip `ValidateSignature` - } - - inline for (@typeInfo(field).Struct.decls) |function| { - try t.expectEqual(ValidateSignature, @TypeOf(&@field(field, function.name))); - } - } -}