From ccb507d4d926c57f2396b87355f9d6063633e441 Mon Sep 17 00:00:00 2001 From: torque Date: Wed, 10 Jul 2024 12:39:18 -0700 Subject: [PATCH] cli: provide more useful help text --- src/main.zig | 191 ++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 157 insertions(+), 34 deletions(-) diff --git a/src/main.zig b/src/main.zig index 52e7e25..3223c5f 100644 --- a/src/main.zig +++ b/src/main.zig @@ -12,18 +12,6 @@ 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(); @@ -35,21 +23,26 @@ pub fn main() !u8 { }; defer std.process.argsFree(allocator, args); - if (args.len < 2) { - printHelp(); + if (args.len < 1) { + printStderr("No arguments at all?", .{}); return 1; } - if (std.mem.eql(u8, args[1], commands.install_udev)) { + const exename = std.fs.path.basename(args[0]); + + if (args.len < 2) { + printHelp(exename, .main); + return 1; + } else if (std.mem.eql(u8, args[1], commands.install_udev)) { if (args.len > 3) { - printHelp(); + printHelp(exename, .install_udev); 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(); + printHelp(exename, .write_config); return 1; } @@ -57,7 +50,7 @@ pub fn main() !u8 { 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(); + printHelp(exename, .run); return 1; } blk: { @@ -85,8 +78,23 @@ pub fn main() !u8 { }; return 0; + } else if (std.mem.eql(u8, args[1], commands.help)) { + if (args.len != 3) { + printHelp(exename, .help); + return 1; + } + + inline for (@typeInfo(@TypeOf(commands)).Struct.fields) |field| { + if (std.mem.eql(u8, args[2], @field(commands, field.name))) { + printHelp(exename, @field(HelpTag, field.name)); + return 0; + } + } else { + printHelp(exename, .help); + return 1; + } } else { - printHelp(); + printHelp(exename, .main); return 1; } } @@ -141,20 +149,135 @@ fn writeDefaultConfig(outarg: ?[]const u8) u8 { 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) }, - ); +fn printHelp(exename: []const u8, comptime cmd: HelpTag) void { + switch (cmd) { + .main => { + printStderr(command_help.main, .{ .exename = exename }); + inline for (@typeInfo(@TypeOf(commands)).Struct.fields) |field| { + printStderr( + " {s: <" ++ max_command_len ++ "} {s}", + .{ @field(commands, field.name), @field(command_help, field.name).brief }, + ); + } + printStderr("", .{}); + }, + .help => { + printStderr(command_help.help.full, .{ .exename = exename, .cmdname = "help" }); + inline for (@typeInfo(@TypeOf(commands)).Struct.fields) |field| { + printStderr( + " - {s}", + .{@field(commands, field.name)}, + ); + } + printStderr("", .{}); + }, + else => { + printStderr( + @field(command_help, @tagName(cmd)).full, + .{ .exename = exename, .cmdname = @field(commands, @tagName(cmd)) }, + ); + printStderr("", .{}); + }, } } + +const HelpTag = std.meta.FieldEnum(@TypeOf(command_help)); + +const commands = .{ + .install_udev = "install-udev-rules", + .write_config = "write-default-config", + .run = "run", + .calibrate = "calibrate", + .help = "help", +}; + +const max_command_len: []const u8 = blk: { + var len: usize = 0; + for (@typeInfo(@TypeOf(commands)).Struct.fields) |field| + if (@field(commands, field.name).len > len) { + len = @field(commands, field.name).len; + }; + break :blk std.fmt.comptimePrint("{d}", .{len}); +}; + +const command_help = .{ + .main = + \\Usage: {[exename]s} [arguments...] + \\ + \\ Calibrate/Control a Yaesu G-5500DC rotator with a LabJack U12. + \\ + \\Commands: + , + .install_udev = .{ + .brief = "Install a udev rules file for the LabJack U12", + .full = + \\Usage: {[exename]s} {[cmdname]s} [] + \\ + \\ Install a udev rules file for the LabJack U12, which allows unprivileged access to the device on + \\ Linux-based operating systems. + \\ + \\Arguments: + \\ rules_dir [Optional] The path to the udev rules directory inside which the rules file will be + \\ written. (Default: /etc/udev/rules.d) + , + }, + .write_config = .{ + .brief = "Write the default configuration to a file", + .full = + \\Usage: {[exename]s} {[cmdname]s} [] + \\ + \\ Write the built-in configuration defaults to a file. Useful as a starting point for creating a + \\ custom configuration. + \\ + \\Arguments: + \\ config_file [Optional] the path of the file to write. (Default: ./yaes.json) + , + }, + .run = .{ + .brief = "Run the rotator with a hamlib-compatible TCP interface", + .full = + \\Usage: {[exename]s} {[cmdname]s} [] + \\ + \\ Expose a hamlib (rotctld)-compatible TCP interface through which the rotator can be controlled. + \\ This listens on localhost port 4533 by default. Only a subset of the rotctld commands are + \\ actually supported. A brief list of supported commands: + \\ + \\ P, set_pos - point the rotator to the given azimuth and elevation + \\ p, get_pos - return the rotator's current azimuth and elevation + \\ S, stop - stop moving the rotator if it is moving + \\ K, park - move the rotator to its parking posture (defined by the config) + \\ q, Q, quit - [nonstandard] stop the rotator control loop and exit + \\ + \\Arguments: + \\ config_file [Optional] the name of the config file to load. If this file does not exist, then + \\ the built-in defaults will be used. (Default: ./yaes.json) + , + }, + .calibrate = .{ + .brief = "Calibrate the rotator's feedback or its orientation to geodetic North", + .full = + \\Usage: {[exename]s} {[cmdname]s} [] + \\ + \\ Perform a calibration routine and write an updated configuration with its results. + \\ + \\Arguments: + \\ routine Must be one of `feedback` or `orientation`. The different calibration routines have + \\ different requirements. `orientation` calibration is a sun-pointing-based routine and + \\ should be performed after `feedback` calibration is complete. + \\ config_file [Optional] the path of a config file to load. This file will be updated with the + \\ results of the calibration process. If omitted and the configuration file does not + \\ exist, then the default configuration will be used. (Default: ./yaes.json) + , + }, + .help = .{ + .brief = "Print detailed help for a given command", + .full = + \\Usage: {[exename]s} {[cmdname]s} + \\ + \\ Print information on how to use a command and exit. + \\ + \\Arguments: + \\ command The name of the command to print information about. Must be one of the following: + , + }, +};