Skip to content

Commit

Permalink
feat(cli)!: use cova for argument parsing
Browse files Browse the repository at this point in the history
  • Loading branch information
charlesrocket committed Aug 20, 2024
1 parent b3985f1 commit 5a94115
Show file tree
Hide file tree
Showing 4 changed files with 202 additions and 95 deletions.
7 changes: 7 additions & 0 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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);
Expand Down
4 changes: 4 additions & 0 deletions build.zig.zon
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
250 changes: 180 additions & 70 deletions src/main.zig
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,156 @@ const tb = @cImport({
@cInclude("termbox2.h");
});

const cova = @import("cova");
const Ghext = @import("ghext");

const Thread = std.Thread;
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,
Expand Down Expand Up @@ -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();
}
Expand All @@ -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);
}
};

Expand All @@ -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);
}

Expand Down Expand Up @@ -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" {
Expand Down
Loading

0 comments on commit 5a94115

Please sign in to comment.