Closed p7r0x7 closed 1 year ago
Looks like a "constness" issue from a glance. Breaking down that error to something more human readable we get:
expected type '?[]Command.Custom(...) found '*const [2]Command.Custom(...)
While a slice can be implicitly coerced from an array pointer, the constness of the inner type must be consistent between the two. When you do the &.{ command(...), command(...) }
it creates two CommandT
instances, but I think since they're being passed as part of a parameter, they ends up being const CommandT
.
You could try changing the sub_cmds
parameter to type []const CommandT
and the same for opts
. However, that might lead into other const issues.
The use of const
in Zig is something I've had to learn over time and it may require changes in the Cova library down the road. Not sure that that affects you directly here though, as this seems to be a conflict with Zig's rules directly.
When you get a chance, try the above fix and let me know how it works out please.
It did work; I tried so many type combinations including const (but I think I tried const with a pointer that time), so I wish the error was easier decipher.
Separate confusion: valType and default_val aren't a thing, can you help me find the correct values for those?
pub fn usage(self: anytype, writer: anytype, _: mem.Allocator) !void {
const long_prefix = @TypeOf(self.*).long_prefix;
try writer.print(
"{?s}{?s} {s}\"{s}({s})\"{s} {s}",
.{ long_prefix, self.long_name, butter, self.val.name(), self.valType, zero, self.default_val },
);
}
I'm working on implementing stuff for #27
I'm very confused as to why some values require @TypeOf(self.*).
instead of just self.
and I don't know enough about Zig to properly read your library.
valType()
was changed to childType()
recently for consistency in the Value code. Also note that it must be called as a function. default_val
is not as easily exposed to the Value.Custom
wrapper. You can only get the default value if you know the child Type. If so, you would do val.generic.[typename_here]. default_val
(unless it's a []const u8
in which case put string
). Tomorrow I can add an abstractions so that it's simply val.getDefaultAs(Type)
, but that will still require the child Type. I've tried fairly extensively to make this easier, but I haven't had much luck yet outside of comptime.
Values are a little weird under the hood because they have to be generic wrappers for any number of underlying child Types that are all treated as one Type. That's why the common Argument Type fields from Command and Option are all function calls in Value. Unfortunately, I haven't found a way to make a function with a dynamic return Type, which is also why childType
only returns the Type Name and not the Type itself.
I'm very confused as to why some values require
@TypeOf(self.*).
instead of justself.
and I don't know enough about Zig to properly read your library.
Would you mind sending an example? I'm guessing it has a little to do with what I said above, but I'd like to see where exactly you mean to give a better answer.
valType()
was changed tochildType()
recently for consistency in the Value code. Also note that it must be called as a function.default_val
is not as easily exposed to theValue.Custom
wrapper. You can only get the default value if you know the child Type. If so, you would doval.generic.[typename_here]. default_val
(unless it's a[]const u8
in which case putstring
). Tomorrow I can add an abstractions so that it's simplyval.getDefaultAs(Type)
, but that will still require the child Type. I've tried fairly extensively to make this easier, but I haven't had much luck yet outside of comptime.Values are a little weird under the hood because they have to be generic wrappers for any number of underlying child Types that are all treated as one Type. That's why the common Argument Type fields from Command and Option are all function calls in Value. Unfortunately, I haven't found a way to make a function with a dynamic return Type, which is also why
childType
only returns the Type Name and not the Type itself.
Damn, so I need to programmatically generate usage funcs for each option, ugh...
I'm very confused as to why some values require
@TypeOf(self.*).
instead of justself.
and I don't know enough about Zig to properly read your library.Would you mind sending an example? I'm guessing it has a little to do with what I said above, but I'd like to see where exactly you mean to give a better answer.
pub fn usage(self: anytype, writer: anytype, _: mem.Allocator) !void {
const long_prefix = @TypeOf(self.*).long_prefix;
_ = long_prefix;
try writer.print(
"{?s}{?s} {s}\"{s}({s})\"{s} {s}",
.{ self.long_prefix, self.long_name, butter, self.val.name(), self.val.childType(), zero, zero },
);
}
This no longer works; removing the self.
from long_prefix would fix it.
Note how this is written this way as well:
pub fn help(self: anytype, writer: anytype, _: mem.Allocator) !void {
try self.usage(writer);
try writer.print(
"\n{?s}{?s}{s}",
.{ @TypeOf(self.*).indent_fmt, @TypeOf(self.*).indent_fmt, self.description },
);
}
const std = @import("std");
const cova = @import("cova");
const io = @import("std").io;
const os = @import("std").os;
const mem = @import("std").mem;
const ascii = @import("std").ascii;
pub usingnamespace @import("cova"); // Forward namespaces from the original module
const blurple = "\x1b[38;5;111m";
const butter = "\x1b[38;5;230m";
const zero = "\x1b[0m";
/// Cova configuration type identity
pub const CommandT = cova.Command.Custom(.{
.indent_fmt = " ",
.subcmds_help_fmt = "{s}:\t" ++ butter ++ "{s}" ++ zero,
.opt_config = .{
.help_fn = struct {
pub fn help(self: anytype, writer: anytype, _: mem.Allocator) !void {
try self.usage(writer);
try writer.print(
"\n{?s}{?s}{s}",
.{ @TypeOf(self.*).indent_fmt, @TypeOf(self.*).indent_fmt, self.description },
);
}
}.help,
.usage_fn = struct {
pub fn usage(self: anytype, writer: anytype, _: mem.Allocator) !void {
try writer.print(
"{?s}{?s} {s}\"{s}({s})\"{s}",
.{ @TypeOf(self.*).long_prefix, self.long_name, butter, self.val.name(), self.val.childType(), zero},
);
}
}.usage,
.allow_abbreviated_long_opts = false,
.allow_opt_val_no_space = true,
.opt_val_seps = "=:",
.short_prefix = null,
.long_prefix = "-",
},
.val_config = .{
.vals_help_fmt = "{s} ({s}):\t" ++ butter ++ "{s}" ++ zero,
.set_behavior = .Last,
.arg_delims = ",;",
},
});
///
pub const vpxl_cmd = command(
"vpxl",
"a VP9 encoder by Matt R Bonnette",
&.{
command("xpsnr", "calculate XPSNR score", null, &.{
pathOption("mkv", "input_path", ""),
pathOption("y4m", "input_path", ""),
pathOption("yuv", "input_path", ""),
}),
command("fssim", "calculate FastSSIM score", null, &.{
pathOption("mkv", "input_path", ""),
pathOption("y4m", "input_path", ""),
pathOption("yuv", "input_path", ""),
}),
},
&.{
pathOption("mkv", "input_path", ""),
pathOption("y4m", "input_path", ""),
pathOption("yuv", "input_path", ""),
pathOption("webm", "output_path", ""),
pathOption("ivf", "output_path", ""),
boolOption("resume", true, "Don't be dummy and disable this, this is necessary for thine happiness <3."),
},
);
fn command(name: []const u8, description: []const u8, sub_cmds: ?[]const CommandT, opts: ?[]const CommandT.OptionT) CommandT {
return .{ .name = name, .description = blurple ++ description ++ zero, .sub_cmds = sub_cmds, .opts = opts };
}
fn boolOption(name: []const u8, default: bool, description: []const u8) CommandT.OptionT {
return .{
.name = name,
.long_name = name,
.description = blurple ++ description ++ zero,
.val = CommandT.ValueT.ofType(bool, .{
.name = "",
.default_val = default,
.parse_fn = struct {
pub fn parseBool(arg: []const u8, _: mem.Allocator) !bool {
const T = [_][]const u8{ "1", "true", "t", "yes", "y" };
const F = [_][]const u8{ "0", "false", "f", "no", "n" };
for (T) |str| if (ascii.eqlIgnoreCase(str, arg)) return true;
for (F) |str| if (ascii.eqlIgnoreCase(str, arg)) return false;
return error.InvalidBooleanValue;
}
}.parseBool,
}),
.usage_fn = struct {
pub fn usage(self: anytype, writer: anytype, _: mem.Allocator) !void {
try writer.print(
"{?s}{?s} {s}\"{s}({s})\"{s} default: {s}",
.{ @TypeOf(self.*).long_prefix, self.long_name, butter, self.val.name(), self.val.childType(), zero, self.val.generic.bool.default_val },
);
}
}.usage,
};
}
fn pathOption(name: []const u8, val: []const u8, description: []const u8) CommandT.OptionT {
return .{
.name = name,
.long_name = name,
.description = blurple ++ description ++ zero,
.val = CommandT.ValueT.ofType([]const u8, .{
.name = val ++ " ",
.parse_fn = struct {
pub fn parsePath(arg: []const u8, _: mem.Allocator) ![]const u8 {
os.access(arg, os.F_OK) catch |err| {
// Windows doesn't make stdin/out/err available via system path,
// so this will have to be handled outside Cova
if (mem.eql(u8, arg, "-")) return arg;
return err;
};
return arg;
}
}.parsePath,
}),
};
}
p7r0x7@Peroxide-2 ~/D/vpxl [2]> zig build -Drelease=true run -- -help
zig build-exe vpxl ReleaseSafe native: error: the following command failed with 1 compilation errors:
/Users/p7r0x7/zig/zig build-exe /Users/p7r0x7/Documents/vpxl/src/main.zig -I/opt/homebrew/Cellar/zimg/3.0.5/include -L/opt/homebrew/Cellar/zimg/3.0.5/lib -lzimg -OReleaseSafe --cache-dir /Users/p7r0x7/Documents/vpxl/zig-cache --global-cache-dir /Users/p7r0x7/.cache/zig --name vpxl --mod cova::/Users/p7r0x7/.cache/zig/p/1220775674b8a20bc2919e1a9181a4dfe69b4263d74740d7d85a807874cb3fd790da/src/cova.zig --deps cova --listen=-
Build Summary: 0/5 steps succeeded; 1 failed (disable with --summary none)
run transitive failure
└─ run vpxl transitive failure
├─ zig build-exe vpxl ReleaseSafe native 1 errors
└─ install transitive failure
└─ install vpxl transitive failure
└─ zig build-exe vpxl ReleaseSafe native (reused)
src/cova.zig:103:11: error: no field named 'usage_fn' in struct 'Option.Custom(.{.val_config = .{.set_behavior = .Last, .arg_delims = ",;", .custom_types = .{ }, .custom_parse_fns = null, .use_custom_bit_width_range = false, .min_int_bit_width = 1, .max_int_bit_width = 256, .help_fn = null, .usage_fn = null, .indent_fmt = " ", .vals_usage_fmt = "\"{s} ({s})\"", .vals_help_fmt = "{s} ({s}):\t\x1b[38;5;230m{s}\x1b[0m"}, .help_fn = (function 'help'), .usage_fn = (function 'usage'), .indent_fmt = " ", .help_fmt = null, .usage_fmt = "[{c}{?c},{s}{?s} \"{s} ({s})\"]", .short_prefix = null, .long_prefix = "-", .allow_opt_val_no_space = true, .opt_val_seps = "=:", .allow_abbreviated_long_opts = false})'
}.usage,
^~~~~
/Users/p7r0x7/.cache/zig/p/1220775674b8a20bc2919e1a9181a4dfe69b4263d74740d7d85a807874cb3fd790da/src/Option.zig:85:12: note: struct declared here
return struct {
^~~~~~
src/cova.zig:70:19: note: called from here
boolOption("resume", true, "Don't be dummy and disable this, this is necessary for thine happiness <3."),
~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
referenced by:
main: src/main.zig:12:32
callMain: /Users/p7r0x7/zig/lib/std/start.zig:583:32
remaining reference traces hidden; use '-freference-trace' to see all reference traces
p7r0x7@Peroxide-2 ~/D/vpxl [2]>
Here I'm trying to implement a custom usage_fn
for every option, as you expressed was necessary.
const std = @import("std");
const cova = @import("cova");
const io = @import("std").io;
const os = @import("std").os;
const mem = @import("std").mem;
const ascii = @import("std").ascii;
pub usingnamespace @import("cova"); // Forward namespaces from the original module
const blurple = "\x1b[38;5;111m";
const butter = "\x1b[38;5;230m";
const zero = "\x1b[0m";
/// Cova configuration type identity
pub const CommandT = cova.Command.Custom(.{
.indent_fmt = " ",
.subcmds_help_fmt = "{s}:\t" ++ butter ++ "{s}" ++ zero,
.opt_config = .{
.help_fn = struct {
pub fn help(self: anytype, writer: anytype, _: mem.Allocator) !void {
try self.usage(writer);
try writer.print(
"\n{?s}{?s}{s}",
.{ @TypeOf(self.*).indent_fmt, @TypeOf(self.*).indent_fmt, self.description },
);
}
}.help,
.usage_fn = struct {
pub fn usage(self: anytype, writer: anytype, _: mem.Allocator) !void {
try writer.print(
@TypeOf(self.*).long_prefix ++ self.long_name ++ " " ++ butter ++ "\"{s} ({s})\"" ++ zero,
.{ self.val.name(), self.val.childType() },
);
}
}.usage,
.allow_abbreviated_long_opts = false,
.allow_opt_val_no_space = true,
.opt_val_seps = "=:",
.short_prefix = null,
.long_prefix = "-",
},
.val_config = .{
.vals_help_fmt = "{s} ({s}):\t" ++ butter ++ "{s}" ++ zero,
.set_behavior = .Last,
.arg_delims = ",;",
},
});
fn command(opt: []const u8, desc: []const u8, sub_cmds: ?[]const CommandT, opts: ?[]const CommandT.OptionT) CommandT {
return .{ .name = opt, .description = blurple ++ desc ++ zero, .sub_cmds = sub_cmds, .opts = opts };
}
fn boolOption(opt: []const u8, default: bool, desc: []const u8) CommandT.OptionT {
return .{
.name = opt,
.long_name = opt,
.description = blurple ++ desc ++ zero,
.val = CommandT.ValueT.ofType(bool, .{
.name = "",
.default_val = default,
.parse_fn = struct {
pub fn parseBool(arg: []const u8, _: mem.Allocator) !bool {
const T = [_][]const u8{ "1", "true", "t", "yes", "y" };
const F = [_][]const u8{ "0", "false", "f", "no", "n" };
for (T) |str| if (ascii.eqlIgnoreCase(str, arg)) return true;
for (F) |str| if (ascii.eqlIgnoreCase(str, arg)) return false;
return error.InvalidBooleanValue;
}
}.parseBool,
}),
//.usage_fn = struct {
// pub fn usage(self: anytype, writer: anytype, _: mem.Allocator) !void {
// try writer.write(@TypeOf(self.*).long_prefix ++ self.long_name ++ " " ++
// butter ++ "\"(" ++ self.val.childType() ++ ")\"" ++ zero ++ self.val.generic.bool.default_val);
// }
//}.usage,
};
}
fn pathOption(opt: []const u8, val: []const u8, desc: []const u8) CommandT.OptionT {
return .{
.name = opt,
.long_name = opt,
.description = desc,
.val = CommandT.ValueT.ofType([]const u8, .{
.name = val ++ "_path",
.parse_fn = struct {
pub fn parsePath(arg: []const u8, _: mem.Allocator) ![]const u8 {
os.access(arg, os.F_OK) catch |err| {
// Windows doesn't make stdin/out/err available via system path,
// so this will have to be handled outside Cova
if (mem.eql(u8, arg, "-")) return arg;
return err;
};
return arg;
}
}.parsePath,
}),
//.usage_fn = struct {
// pub fn usage(self: anytype, writer: anytype, _: mem.Allocator) !void {
// try writer.print(
// @TypeOf(self.*).long_prefix ++ self.long_name ++ " " ++ butter ++ "\"{s} ({s})\"" ++ zero,
// .{ self.val.name(), self.val.childType() },
// );
// }
//}.usage,
};
}
pub fn runVPXL(writer: anytype, ally: mem.Allocator) !void {
const vpxl_cmd = comptime command(
"vpxl",
"a VP9 encoder by Matt R Bonnette",
&.{
command("xpsnr", "calculate XPSNR score", null, &.{
pathOption("mkv", "input", ""),
pathOption("y4m", "input", ""),
pathOption("yuv", "input", ""),
}),
command("fssim", "calculate FastSSIM score", null, &.{
pathOption("mkv", "input", ""),
pathOption("y4m", "input", ""),
pathOption("yuv", "input", ""),
}),
},
&.{
pathOption("mkv", "input", ""),
pathOption("y4m", "input", ""),
pathOption("yuv", "input", ""),
pathOption("webm", "output", ""),
pathOption("ivf", "output", ""),
boolOption("resume", true, "Don't be dummy and disable this, this is necessary for thine happiness <3."),
},
);
const vpxl_cli = &(try vpxl_cmd.init(ally, .{}));
defer vpxl_cli.deinit();
var arg_it = try cova.ArgIteratorGeneric.init(ally);
defer arg_it.deinit();
cova.parseArgs(&arg_it, CommandT, vpxl_cli, writer, .{}) catch |err| switch (err) {
error.UsageHelpCalled,
error.TooManyValues,
error.UnrecognizedArgument,
error.UnexpectedArgument,
error.CouldNotParseOption,
=> {},
else => return err,
};
const in_fmt = try vpxl_cli.matchOpts(&.{ "mkv", "y4m", "yuv" }, .{ .logic = .XOR });
_ = in_fmt;
const out_fmt = try vpxl_cli.matchOpts(&.{ "webm", "ivf" }, .{ .logic = .XOR });
_ = out_fmt;
// Handle in_fmt and out_fmt
//if (builtin.mode == .Debug) try stderr.print("{s}\n{s}\n", .{ in_fmt[0].name, out_fmt[0].name });
//if (builtin.mode == .Debug) try cli.utils.displayCmdInfo(cova.CommandT, vpxl_cli, ally, stderr);
}
const std = @import("std");
const io = @import("std").io;
const cli = @import("cli.zig");
const mem = @import("std").mem;
const heap = @import("std").heap;
const builtin = @import("builtin");
pub fn main() !void {
// Heapspace Initialization
const ally: mem.Allocator = ally: {
if (builtin.mode == .Debug) {
var gpa = heap.GeneralPurposeAllocator(.{}){};
break :ally heap.ArenaAllocator.init(gpa.allocator()).allocator();
// arena of gpa
} else {
var arena = heap.ArenaAllocator.init(heap.page_allocator);
var sfa = heap.stackFallback(8 << 20, arena.allocator());
break :ally sfa.get();
// stack then arena of page
}
};
defer ally.deinit();
// Connect to pipes & run VPXL's CLI
var se = io.bufferedWriter(io.getStdErr().writer());
const stderr = se.writer();
try cli.runVPXL(stderr, ally);
}
Edit, please pay more attention the where I should be adding the custom usage funcs for each option than to anything else, I fixed an error from before.
Ok, I see the issue here, and it's something I could try and improve the documentation for. The usage_fn
for Option.Config
(along with all fields in that struct) are meant for attributes that apply to ALL instances of the generated Option Type OptionT
. Because of this, the function cannot be applied to individual instances of an OptionT as you're trying above. This was the change I had mentioned making in #32 here.
Conversely, the parse_fn
you're using in .val = CommandT.ValueT.ofType([]const u8, .{...});
and similar are applied directly to an instance of ValueT, so there's no need for @TypeOf(self)
because it's handled by that instance directly.
Two solutions here. One, you can do a conditional statement in the function that covers the ValueT child Types you're trying to cover. Two, I can implement the change above.
I see what you're saying, but I don't know the best solution for make the default values for each option available in its usage_fn
Edit: as stated in #32, I'm going to attempt to use a global function with a well-written switch statement; frankly, I feel silly not thinking of doing that sooner (especially because you've already suggested it, I just kept failing to consider it).
No worries! I'm interested to see how your solution works out.
Closing this unconventional issue.