diff --git a/build.zig b/build.zig index c6c1e37..89910c9 100644 --- a/build.zig +++ b/build.zig @@ -11,6 +11,12 @@ pub fn build(b: *std.Build) void { .optimize = optimize, }); + const cova_dep = b.dependency("cova", .{ + .target = target, + }); + + const cova = cova_dep.module("cova"); + const ghext_dep = b.dependency("ghext", .{ .target = target, .optimize = optimize, @@ -21,6 +27,7 @@ pub fn build(b: *std.Build) void { exe.addIncludePath(b.dependency("termbox2", .{}).path(".")); exe.addCSourceFile(.{ .file = b.path("src/termbox_impl.c") }); exe.linkLibC(); + exe.root_module.addImport("cova", cova); exe.root_module.addImport("ghext", ghext); b.installArtifact(exe); diff --git a/build.zig.zon b/build.zig.zon index 60b3780..58da37f 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -7,6 +7,10 @@ .url = "https://github.com/termbox/termbox2/archive/d4128b49ca4b4c934c5f07b8b7d734421a4a76a0.zip", .hash = "12200705c160371d843e7a806133f3f803d8da9af8a50c4f43dea7cec3264de1d1c7", }, + .cova = .{ + .url = "https://github.com/00JCIV00/cova/archive/b49e22d8f4727a92e86c80932df7a034e5498295.tar.gz", + .hash = "12200ac5cfa52c6ed5df6cbbf6be03f5b5b4a95abe67b750f9f2b0216da11b21e70c", + }, .ghext = .{ .url = "https://github.com/charlesrocket/ghext/archive/refs/tags/0.3.0.tar.gz", .hash = "12200acee906e217dafde5539a1e5905d093c97b6ba2408e0653772814e3643efc76", diff --git a/src/main.zig b/src/main.zig index 09c7cc0..4150b25 100644 --- a/src/main.zig +++ b/src/main.zig @@ -3,6 +3,7 @@ const tb = @cImport({ @cInclude("termbox2.h"); }); +const cova = @import("cova"); const Ghext = @import("ghext"); const Thread = std.Thread; @@ -10,11 +11,148 @@ const Mutex = Thread.Mutex; const log = std.log.scoped(.xtxf); const assets = @import("assets.zig"); +pub const Error = error{ + InvalidColor, + InvalidStyle, + IndalidMode, +}; + +const FRAME = 39730492; + const Mode = enum { binary, decimal }; const Style = enum { default, columns, crypto, grid, blocks }; const Color = enum { default, red, green, blue, yellow, magenta }; +const CommandT = cova.Command.Custom(.{ + .val_config = .{ + .custom_types = &.{ + Color, + Style, + Mode, + }, + .child_type_parse_fns = &.{ + .{ + .ChildT = Color, + .parse_fn = struct { + pub fn parseColor(color: [:0]const u8) !Color { + if (eqlStr("default", color)) { + return Color.default; + } else if (eqlStr("red", color)) { + return Color.red; + } else if (eqlStr("green", color)) { + return Color.green; + } else if (eqlStr("blue", color)) { + return Color.blue; + } else if (eqlStr("yellow", color)) { + return Color.yellow; + } else if (eqlStr("magenta", color)) { + return Color.magenta; + } else { + return error.InvalidColor; + } + } + }.parseColor, + }, + .{ + .ChildT = Style, + .parse_fn = struct { + pub fn parseStyle(style: [:0]const u8) !Style { + if (eqlStr("default", style)) { + return Style.default; + } else if (eqlStr("columns", style)) { + return Style.columns; + } else if (eqlStr("crypto", style)) { + return Style.crypto; + } else if (eqlStr("grid", style)) { + return Style.grid; + } else if (eqlStr("blocks", style)) { + return Style.blocks; + } else { + return error.InvalidStyle; + } + } + }.parseStyle, + }, + .{ + .ChildT = Mode, + .parse_fn = struct { + pub fn parseMode(mode: [:0]const u8) !Mode { + if (eqlStr("binary", mode)) { + return Mode.binary; + } else if (eqlStr("decimal", mode)) { + return Mode.decimal; + } else { + return error.InvalidMode; + } + } + }.parseMode, + }, + }, + }, +}); -const FRAME = 39730492; +const ValueT = CommandT.ValueT; +const setup_cmd: CommandT = .{ + .name = "xtxf", + .description = "Binary matrix.", + + .opts = &.{ + .{ + .name = "color_opt", + .description = "Set output color", + .short_name = 'c', + .long_name = "color", + .val = ValueT.ofType(Color, .{ + .name = "STRING", + .description = "A string value.", + .default_val = Color.default, + }), + }, + .{ + .name = "style_opt", + .description = "Set output style.", + .short_name = 's', + .long_name = "style", + .val = ValueT.ofType(Style, .{ + .name = "STRING", + .description = "A string value.", + .default_val = Style.default, + }), + }, + .{ + .name = "mode_opt", + .description = "Set symbol mode.", + .short_name = 'm', + .long_name = "mode", + .val = ValueT.ofType(Mode, .{ + .name = "STRING", + .description = "STRING", + .default_val = Mode.binary, + }), + }, + .{ + .name = "time_opt", + .description = "Set duration (in seconds).", + .short_name = 't', + .long_name = "time", + .val = ValueT.ofType(u32, .{ + .name = "INTEGER", + .description = "INTEGER", + .default_val = 0, + }), + }, + .{ + .name = "pulse_opt", + .description = "Enable pulse blocks.", + .short_name = 'p', + .long_name = "pulse", + .val = ValueT.ofType(bool, .{ + .name = "FLAG", + .description = "Flag", + .default_val = false, + }), + }, + }, +}; const Core = struct { allocator: std.mem.Allocator, @@ -102,7 +240,7 @@ const Core = struct { try self.updateTermSize(); } - fn shutdown(self: *Core, args: [][:0]u8, allocator: *std.heap.GeneralPurposeAllocator(.{})) void { + fn shutdown(self: *Core) void { if (!self.active) { _ = tb.tb_shutdown(); } @@ -114,11 +252,6 @@ const Core = struct { if (self.height_gaps != null) { self.height_gaps.?.deinit(); } - - std.process.argsFree(self.allocator, args); - _ = allocator.deinit(); - - std.process.exit(0); } }; @@ -142,12 +275,12 @@ const Handler = struct { try core.updateStyle(self.style); var timer = try std.time.Timer.start(); - const duration = self.duration * 1000000000; + const duration = self.duration; self.setHalt(false); while (core.active) { - if (timer.read() >= duration and self.duration != 0) { + if ((timer.read() / std.time.ns_per_s) >= duration and self.duration != 0) { core.setActive(false); } @@ -297,84 +430,61 @@ fn eqlStr(a: [:0]const u8, b: [:0]const u8) bool { pub fn main() !void { var gpallocator = std.heap.GeneralPurposeAllocator(.{}){}; + defer _ = gpallocator.deinit(); + var core = Core.init(gpallocator.allocator()); var handler = Handler.init(); - const args = try std.process.argsAlloc(core.allocator); + const stdout = std.io.getStdOut().writer(); + const main_cmd = try setup_cmd.init(core.allocator, .{}); + defer main_cmd.deinit(); - for (args) |arg| { - if (eqlStr(arg, "--help") or eqlStr(arg, "-h")) { - const stdout = std.io.getStdOut(); - try stdout.writer().print("{s}{s}", .{ assets.logo ++ "\n" ++ assets.help_message, "\n" }); + var args_iter = try cova.ArgIteratorGeneric.init(core.allocator); + defer args_iter.deinit(); - core.shutdown(args, &gpallocator); - } + cova.parseArgs(&args_iter, CommandT, main_cmd, stdout, .{}) catch |err| switch (err) { + error.UsageHelpCalled => {}, + else => return err, + }; - if (eqlStr(arg, "--color=default") or eqlStr(arg, "-c=default")) { - core.color = Color.default; - } else if (eqlStr(arg, "--color=red") or eqlStr(arg, "-c=red")) { - core.color = Color.red; - } else if (eqlStr(arg, "--color=green") or eqlStr(arg, "-c=green")) { - core.color = Color.green; - } else if (eqlStr(arg, "--color=blue") or eqlStr(arg, "-c=blue")) { - core.color = Color.blue; - } else if (eqlStr(arg, "--color=yellow") or eqlStr(arg, "-c=yellow")) { - core.color = Color.yellow; - } else if (eqlStr(arg, "--color=magenta") or eqlStr(arg, "-c=magenta")) { - core.color = Color.magenta; - } + if ((try main_cmd.getOpts(.{})).get("color_opt")) |color_opt| { + core.color = try color_opt.val.getAs(Color); + } - if (eqlStr(arg, "--decimal") or eqlStr(arg, "-d")) { - handler.mode = Mode.decimal; - } + if ((try main_cmd.getOpts(.{})).get("style_opt")) |style_opt| { + handler.style = try style_opt.val.getAs(Style); + } - if (eqlStr(arg, "--pulse") or eqlStr(arg, "-p")) { - core.pulse = true; - } + if ((try main_cmd.getOpts(.{})).get("mode_opt")) |mode_opt| { + handler.mode = try mode_opt.val.getAs(Mode); + } - if (eqlStr(arg, "--time=short") or eqlStr(arg, "-t=short")) { - handler.duration = 1; - } + if ((try main_cmd.getOpts(.{})).get("time_opt")) |time_opt| { + handler.duration = try time_opt.val.getAs(u32); + } - if (eqlStr(arg, "--style=crypto") or eqlStr(arg, "-s=crypto")) { - handler.style = Style.crypto; - } else if (eqlStr(arg, "--style=columns") or eqlStr(arg, "-s=columns")) { - handler.style = Style.columns; - } else if (eqlStr(arg, "--style=blocks") or eqlStr(arg, "-s=blocks")) { - handler.style = Style.blocks; - } else if (eqlStr(arg, "--style=grid") or eqlStr(arg, "-s=grid")) { - handler.style = Style.grid; - } + if ((try main_cmd.getOpts(.{})).get("pulse_opt")) |pulse_opt| { + core.pulse = try pulse_opt.val.getAs(bool); + } - if (eqlStr(arg, "--version") or eqlStr(arg, "-v")) { - // TODO add SemVer string - var gxt = try Ghext.read(core.allocator); - const stdout = std.io.getStdOut(); - try stdout.writer().print("{s}{s}{s}", .{ "xtxf ", gxt.hash[0..7], "\n" }); + if (!main_cmd.checkOpts(&.{ "help", "version" }, .{})) { + core.start(); - gxt.deinit(core.allocator); - core.shutdown(args, &gpallocator); + if (core.width < 4 or core.height < 2) { + core.setActive(false); + log.warn("Insufficient terminal dimensions: W {}, H {}", .{ core.width, core.height }); } - } - core.start(); + if (core.active) { + const t_h = try std.Thread.spawn(.{}, Handler.run, .{ &handler, &core }); + defer t_h.join(); - if (core.width < 4 or core.height < 2) { - core.setActive(false); - core.shutdown(args, &gpallocator); - log.warn("Insufficient terminal dimensions: W {}, H {}", .{ core.width, core.height }); - std.process.exit(0); - } - - if (core.active) { - const t_h = try std.Thread.spawn(.{}, Handler.run, .{ &handler, &core }); - defer t_h.join(); + const t_a = try std.Thread.spawn(.{}, animation, .{ &handler, &core }); + defer t_a.join(); + } - const t_a = try std.Thread.spawn(.{}, animation, .{ &handler, &core }); - defer t_a.join(); + core.shutdown(); } - - core.shutdown(args, &gpallocator); } test "handler" { diff --git a/test/cli.zig b/test/cli.zig index 278d5d4..d00d517 100644 --- a/test/cli.zig +++ b/test/cli.zig @@ -16,91 +16,77 @@ fn runner(args: anytype) !std.process.Child.Term { } test "default" { - const argv = [_][]const u8{ exe_path, "--time=short", "-s=default", "-c=default" }; + const argv = [_][]const u8{ exe_path, "--time=1", "-s=default", "-c=default" }; const term = try runner(argv); try std.testing.expectEqual(term, std.process.Child.Term{ .Exited = 0 }); } test "color: red" { - const argv = [_][]const u8{ exe_path, "--time=short", "-c=red" }; + const argv = [_][]const u8{ exe_path, "--time=1", "-c=red" }; const term = try runner(argv); try std.testing.expectEqual(term, std.process.Child.Term{ .Exited = 0 }); } test "color: green" { - const argv = [_][]const u8{ exe_path, "--time=short", "-c=green" }; + const argv = [_][]const u8{ exe_path, "--time=1", "-c=green" }; const term = try runner(argv); try std.testing.expectEqual(term, std.process.Child.Term{ .Exited = 0 }); } test "color: blue" { - const argv = [_][]const u8{ exe_path, "--time=short", "-c=blue" }; + const argv = [_][]const u8{ exe_path, "--time=1", "-c=blue" }; const term = try runner(argv); try std.testing.expectEqual(term, std.process.Child.Term{ .Exited = 0 }); } test "color: yellow" { - const argv = [_][]const u8{ exe_path, "--time=short", "-c=yellow" }; + const argv = [_][]const u8{ exe_path, "--time=1", "-c=yellow" }; const term = try runner(argv); try std.testing.expectEqual(term, std.process.Child.Term{ .Exited = 0 }); } test "color: magenta" { - const argv = [_][]const u8{ exe_path, "--time=short", "-c=magenta" }; + const argv = [_][]const u8{ exe_path, "--time=1", "-c=magenta" }; const term = try runner(argv); try std.testing.expectEqual(term, std.process.Child.Term{ .Exited = 0 }); } test "columns" { - const argv = [_][]const u8{ exe_path, "--time=short", "-s=columns" }; + const argv = [_][]const u8{ exe_path, "--time=1", "-s=columns" }; const term = try runner(argv); try std.testing.expectEqual(term, std.process.Child.Term{ .Exited = 0 }); } test "crypto" { - const argv = [_][]const u8{ exe_path, "--time=short", "-s=crypto" }; + const argv = [_][]const u8{ exe_path, "--time=1", "-s=crypto" }; const term = try runner(argv); try std.testing.expectEqual(term, std.process.Child.Term{ .Exited = 0 }); } test "grid" { - const argv = [_][]const u8{ exe_path, "--time=short", "-s=grid" }; + const argv = [_][]const u8{ exe_path, "--time=1", "-s=grid" }; const term = try runner(argv); try std.testing.expectEqual(term, std.process.Child.Term{ .Exited = 0 }); } test "blocks" { - const argv = [_][]const u8{ exe_path, "--time=short", "-s=blocks" }; + const argv = [_][]const u8{ exe_path, "--time=1", "-s=blocks" }; const term = try runner(argv); try std.testing.expectEqual(term, std.process.Child.Term{ .Exited = 0 }); } test "decimal" { - const argv = [_][]const u8{ exe_path, "--time=short", "--decimal" }; - const term = try runner(argv); - - try std.testing.expectEqual(term, std.process.Child.Term{ .Exited = 0 }); -} - -test "version" { - const argv = [_][]const u8{ exe_path, "-v" }; - const term = try runner(argv); - - try std.testing.expectEqual(term, std.process.Child.Term{ .Exited = 0 }); -} - -test "help" { - const argv = [_][]const u8{ exe_path, "-h" }; + const argv = [_][]const u8{ exe_path, "--time=1", "-m=decimal" }; const term = try runner(argv); try std.testing.expectEqual(term, std.process.Child.Term{ .Exited = 0 });