feat(comma): Added help method.

This commit is contained in:
2026-04-22 10:24:28 -04:00
parent a547409efd
commit 217bb41394
3 changed files with 113 additions and 5 deletions

View File

@@ -1,6 +1,6 @@
//! By convention, root.zig is the root source file when making a package.
const std = @import("std");
// const Io = std.Io;
const Io = std.Io;
pub const Command = struct {
name: []const u8,
@@ -8,7 +8,10 @@ pub const Command = struct {
long: ?[]const u8 = null,
subcommands: []const Command = &.{},
examples: [][]const u8 = &.{},
/// The enum type of the command
Type: type,
/// The type of struct that holds the Commands's flags and arguments
// Params: type,
pub fn new(cmd: CommandOptions) Command {
const subcommands: [cmd.subcommands.len]Command = blk: {
@@ -43,6 +46,65 @@ pub const Command = struct {
return @enumFromInt(self.subcommands.len + 1);
}
/// Used for indentation when printing command help
const tab = " ";
/// Print usage information to the console.
pub fn help(self: @This(), w: *Io.Writer) !void {
defer w.flush() catch {};
if (self.long) |long| {
try w.print("{s}\n\n", .{long});
}
try w.print("Usage:\n{s}{s}\n", .{ tab, self.name });
if (self.subcommands.len > 0) {
try w.print("\nAvailable Commands:\n", .{});
var max_width: u8 = 0;
inline for (self.subcommands) |cmd| {
max_width = @max(max_width, cmd.name.len);
}
// Print short command description
inline for (self.subcommands) |cmd| {
try w.print(
"{s}{s}",
.{
tab,
cmd.name,
},
);
for (0..(max_width - cmd.name.len)) |_| {
try w.print(" ", .{});
}
try w.print(
" {s}\n",
.{
cmd.short orelse "",
},
);
}
try w.print("\n", .{});
}
// TODO: Print flags
// TODO: Print arguments
if (self.subcommands.len > 0) {
try w.print(
"Use \"{s} [command] --help\" for more information about a command.",
.{self.name},
);
}
}
};
pub const ParseError = error{
@@ -78,3 +140,7 @@ const CommandOptions = struct {
);
}
};
// /// parses the args into params
// pub fn params(cmd: Command, args: [][]const u8) cmd.Params {
// }

View File

@@ -26,8 +26,12 @@ fn run(
) !void {
const cmd = envr.root.parse(args[1..]);
switch (cmd) {
.envr, .unknown => {
return fallback_to_go(io, arena, args);
.envr => {
var stdout_buffer: [4096]u8 = undefined;
var stdout_file_writer: Io.File.Writer = .init(.stdout(), io, &stdout_buffer);
const stdout_writer = &stdout_file_writer.interface;
return envr.root.help(stdout_writer);
},
.version => {
var stdout_buffer: [1024]u8 = undefined;
@@ -47,6 +51,9 @@ fn run(
environ_map.get("PATH").?,
);
},
.unknown => {
return fallback_to_go(io, arena, args);
},
}
}

View File

@@ -7,6 +7,38 @@ const Command = comma.Command;
pub const root: Command = .new(.{
.name = "envr",
.short = "Manage your .env files.",
.long =
\\envr keeps your .env synced to a local, age encrypted database.
\\It is a safe and eay way to gather all your .env files in one place where they can
\\easily be backed by another tool such as restic or git.
\\All your data is stored in ~/data.age
\\
\\Getting started is easy:
\\
\\1. Create your configuration file and set up encrypted storage:
\\
\\> envr init
\\
\\2. Scan for existing .env files:
\\
\\> envr scan
\\
\\Select the files you want to back up from the interactive list.
\\
\\3. Verify that it worked:
\\
\\> envr list
\\
\\4. After changing any of your .env files, update the backup with:
\\
\\> envr sync
\\
\\5. If you lose a repository, after re-cloning the repo into the same path it was
\\at before, restore your backup with:
\\
\\> envr restore <path to repository> .env
,
.subcommands = &.{
.{
.name = "deps",
@@ -17,7 +49,10 @@ pub const root: Command = .new(.{
\\ The deps command reports which binaries are available and which are not."
,
},
.{ .name = "version" },
.{
.name = "version",
.short = "Show envr's version",
},
},
});
@@ -35,7 +70,7 @@ test "parse version" {
}
test "parse unknown" {
const args = &[_][]const u8{"bad", "value"};
const args = &[_][]const u8{ "bad", "value" };
const cmd = root.parse(args);
try std.testing.expectEqual(.unknown, cmd);