From 33b0063c6714193061503cbcd68a2918728c7a01 Mon Sep 17 00:00:00 2001 From: Spencer Brower Date: Mon, 20 Apr 2026 16:14:37 -0400 Subject: [PATCH] feat: Added command structure. --- src/main.zig | 36 ++++++++++++-------- src/root.zig | 95 ++++++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 107 insertions(+), 24 deletions(-) diff --git a/src/main.zig b/src/main.zig index e9f0fa8..0056943 100644 --- a/src/main.zig +++ b/src/main.zig @@ -13,28 +13,36 @@ pub fn main(init: std.process.Init) !void { const args = try init.minimal.args.toSlice(arena); - if (args.len > 1 and std.mem.eql(u8, args[1], "version")) { - return version(init.io); - } else { - return fallback_to_go(init.io, args, arena); + run(init.io, args) catch return fallback_to_go(init.io, arena, args); +} + +/// Attempt to run the requested command. +fn run(io: Io, args: []const [:0]const u8) !void { + const cmd = try envr.root.parse(args[1..]); + switch (cmd) { + .envr => { + // TODO: Print help + return envr.ParseError.InvalidType; + }, + .version => { + var stdout_buffer: [1024]u8 = undefined; + var stdout_file_writer: Io.File.Writer = .init(.stdout(), io, &stdout_buffer); + const stdout_writer = &stdout_file_writer.interface; + + return version(stdout_writer); + }, } } -fn version(io: Io) !void { - // std.debug.print("hello from Zig!\n", .{}); - - var stdout_buffer: [1024]u8 = undefined; - var stdout_file_writer: Io.File.Writer = .init(.stdout(), io, &stdout_buffer); - const stdout_writer = &stdout_file_writer.interface; - - try stdout_writer.print("{s}\n", .{config.version}); - try stdout_writer.flush(); +fn version(writer: *Io.Writer) !void { + try writer.print("{s}\n", .{config.version}); + try writer.flush(); } fn fallback_to_go( io: Io, - args: []const [:0]const u8, arena: std.mem.Allocator, + args: []const [:0]const u8, ) std.process.ReplaceError { // Remap args var childArgs = try std.ArrayList([]const u8).initCapacity(arena, args.len); diff --git a/src/root.zig b/src/root.zig index 5a71250..450274f 100644 --- a/src/root.zig +++ b/src/root.zig @@ -2,17 +2,92 @@ const std = @import("std"); const Io = std.Io; -/// This is a documentation comment to explain the `printAnotherMessage` function below. -/// -/// Accepting an `Io.Writer` instance is a handy way to write reusable code. -pub fn printAnotherMessage(writer: *Io.Writer) Io.Writer.Error!void { - try writer.print("Run `zig build test` to run the tests.\n", .{}); +pub const root: Command = .new(.{ + .name = "envr", + .subcommands = &[_]CommandOptions{.{ .name = "version" }}, +}); + +const CommandOptions = struct { + name: []const u8, + short: ?[]const u8 = null, + long: ?[]const u8 = null, + subcommands: []const CommandOptions = &[0]CommandOptions{}, + + fn as_enum(self: @This()) type { + var field_names: [self.subcommands.len + 1][]const u8 = undefined; + var field_values: [self.subcommands.len + 1]u32 = undefined; + + field_names[0] = self.name; + field_values[0] = 0; + + inline for (self.subcommands, 1..) |cmd, idx| { + field_names[idx] = cmd.name; + field_values[idx] = idx; + } + + return @Enum( + u32, + .exhaustive, + &field_names, + &field_values, + ); + } +}; + +pub const Command = struct { + name: []const u8, + short: ?[]const u8 = null, + long: ?[]const u8 = null, + subcommands: []const Command = &[0]Command{}, + Type: type, + + pub fn new(cmd: CommandOptions) Command { + const subcommands: [cmd.subcommands.len]Command = blk: { + var result: [cmd.subcommands.len]Command = undefined; + inline for (cmd.subcommands, 0..) |sub, idx| { + result[idx] = new(sub); + } + break :blk result; + }; + + return .{ + .name = cmd.name, + .short = cmd.short, + .long = cmd.long, + .subcommands = &subcommands, + .Type = cmd.as_enum(), + }; + } + + pub fn parse(comptime self: @This(), args: []const []const u8) ParseError!self.Type { + if (args.len == 0) { + return ParseError.InvalidType; + } + const target = args[0]; + + inline for (self.subcommands, 1..) |cmd, idx| { + if (std.mem.eql(u8, target, cmd.name)) { + return @enumFromInt(idx); + } + } + + return @enumFromInt(0); + } +}; + +pub const ParseError = error{ + InvalidType, +}; + +test "enum type" { + const got: root.Type = @enumFromInt(1); + + try std.testing.expectEqual(.version, got); } -pub fn add(a: i32, b: i32) i32 { - return a + b; -} +test "parse version" { + const args = &[_][]const u8{"version"}; + const cmd = root.parse(args); -test "basic add functionality" { - try std.testing.expect(add(3, 7) == 10); + try std.testing.expectEqual(.version, cmd); }