From 8fb6032a047015e0bcb3da211e61beb07dcb1855 Mon Sep 17 00:00:00 2001 From: torque Date: Sun, 7 Jul 2024 15:37:53 -0700 Subject: [PATCH] main: add very basic command line interface There are three commands: one to write the default config, one to write the embedded udev rules file, and one to actually run the program. I might reformat the help text at some point. It's not very nice as-is. --- src/main.zig | 148 ++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 134 insertions(+), 14 deletions(-) diff --git a/src/main.zig b/src/main.zig index d14a265..a7a40de 100644 --- a/src/main.zig +++ b/src/main.zig @@ -8,33 +8,153 @@ const udev = @import("udev_rules"); const log = std.log.scoped(.main); +fn printStderr(comptime fmt: []const u8, args: anytype) void { + std.debug.print(fmt ++ "\n", args); +} + +const commands = .{ + .install_udev = "install-udev-rules", + .write_config = "write-default-config", + .run = "run", +}; + +const command_help = .{ + .install_udev = "[udev rules.d path]: Install the built-in LabJack u12 udev rules file. May require sudo privileges. Linux only.", + .write_config = "[path]: write the default configuration to a json file.", + .run = "[config path]: run the rotctl interface with the provided config.", +}; + pub fn main() !u8 { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; defer _ = gpa.deinit(); const allocator = gpa.allocator(); - blk: { - const conf_file = std.fs.cwd().openFile("yaes.json", .{}) catch { - log.warn("Could not load config file yaes.json. Using default config.", .{}); - Config.loadDefault(allocator); - break :blk; - }; - defer conf_file.close(); + const args = std.process.argsAlloc(allocator) catch { + printStderr("Couldn't allocate arguments array", .{}); + return 1; + }; + defer std.process.argsFree(allocator, args); - Config.load(allocator, conf_file.reader()) catch { - log.err("Could not parse config file yaes.json. Good luck figuring out why.", .{}); + if (args.len < 2) { + printHelp(); + return 1; + } + + if (std.mem.eql(u8, args[1], commands.install_udev)) { + if (args.len > 3) { + printHelp(); + return 1; + } + + return installUdevRules(if (args.len == 3) args[2] else null); + } else if (std.mem.eql(u8, args[1], commands.write_config)) { + if (args.len > 3) { + printHelp(); + return 1; + } + + Config.loadDefault(allocator); + return writeDefaultConfig(if (args.len == 3) args[2] else null); + } else if (std.mem.eql(u8, args[1], commands.run)) { + if (args.len > 3) { + printHelp(); + return 1; + } + blk: { + const confpath = if (args.len == 3) args[2] else "yaes.json"; + const conf_file = std.fs.cwd().openFile(confpath, .{}) catch { + log.warn("Could not load config file '{s}'. Using default config.", .{confpath}); + Config.loadDefault(allocator); + break :blk; + }; + defer conf_file.close(); + + Config.load(allocator, conf_file.reader()) catch |err| { + log.err("Could not parse config file '{s}': {s}.", .{ confpath, @errorName(err) }); + return 1; + }; + } + defer Config.destroy(allocator); + + const ver = lj.getDriverVersion(); + std.debug.print("Driver version: {d}\n", .{ver}); + + RotCtl.run(allocator) catch |err| { + log.err("rotator controller ceased unexpectedly! {s}", .{@errorName(err)}); return 1; }; + + return 0; + } else { + printHelp(); + return 1; } - defer Config.destroy(allocator); +} - const ver = lj.getDriverVersion(); - std.debug.print("Driver version: {d}\n", .{ver}); +fn installUdevRules(outpath: ?[]const u8) u8 { + const rules_path = outpath orelse "/etc/udev/rules.d"; + var rules_d = std.fs.cwd().openDir(rules_path, .{}) catch |err| { + printStderr( + "could not open udev rules path '{s}': {s}", + .{ rules_path, @errorName(err) }, + ); + return 1; + }; + defer rules_d.close(); - RotCtl.run(allocator) catch |err| { - log.err("rotator controller ceased unexpectedly! {s}", .{@errorName(err)}); + rules_d.writeFile(.{ .sub_path = udev.rules_filename, .data = udev.rules }) catch |err| { + printStderr( + "could not write rules file '{s}{s}{s}': {s}", + .{ + rules_path, + if (rules_path.len == 0) + "./" + else if (rules_path[rules_path.len - 1] == '/') + "" + else + "/", + udev.rules_filename, + @errorName(err), + }, + ); return 1; }; return 0; } + +fn writeDefaultConfig(outarg: ?[]const u8) u8 { + const outpath = outarg orelse "yaes.json"; + + const outfile = std.fs.cwd().createFile(outpath, .{}) catch |err| { + printStderr("Could not write config file '{s}': {s}", .{ outpath, @errorName(err) }); + return 1; + }; + defer outfile.close(); + + std.json.stringify(Config.global.*, .{ .whitespace = .indent_4 }, outfile.writer()) catch |err| { + printStderr("Could not serialize config file '{s}': {s}", .{ outpath, @errorName(err) }); + return 1; + }; + + printStderr("config written to {s}", .{outpath}); + return 0; +} + +fn printHelp() void { + printStderr( + \\Usage: yaes [command] [args...] + \\ + \\ Control a Yaesu G-5500DC rotator through a LabJack U12 using the hamlib TCP interface. + \\ + \\Available commands: + \\ + , .{}); + + inline for (@typeInfo(@TypeOf(commands)).Struct.fields) |field| { + printStderr( + " {s} {s}", + .{ @field(commands, field.name), @field(command_help, field.name) }, + ); + } +}