Backlog/lib/zargs/todo/QUICK_START.md

11 KiB

Implementation Quick Start

Day 1 Morning: Setup

1. Create Directory Structure (5 minutes)

cd /home/sear/Backlog/lib/zargs
mkdir -p src tests examples

2. Create Initial Files (5 minutes)

touch src/main.zig
touch src/ArgumentType.zig
touch src/errors.zig
touch tests/type_test.zig
touch build.zig

3. Setup build.zig (15 minutes)

const std = @import("std");

pub fn build(b: *std.Build) void {
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});

    // Library module
    const zargs = b.addModule("zargs", .{
        .root_source_file = b.path("src/main.zig"),
    });

    // Tests
    const tests = b.addTest(.{
        .root_source_file = b.path("tests/type_test.zig"),
        .target = target,
        .optimize = optimize,
    });
    tests.root_module.addImport("zargs", zargs);

    const run_tests = b.addRunArtifact(tests);
    const test_step = b.step("test", "Run tests");
    test_step.dependOn(&run_tests.step);
}

4. Verify Setup (2 minutes)

zig build test
# Should compile (no tests yet)

Day 1 Afternoon: ArgumentType (Phase 1.1)

Step 1: Write Test First (30 minutes)

File: tests/type_test.zig

const std = @import("std");
const testing = std.testing;
const ArgumentType = @import("ArgumentType.zig").ArgumentType;

test "ArgumentType.fromZigType - bool" {
    const t = ArgumentType.fromZigType(bool);
    try testing.expectEqual(.bool, t);
}

test "ArgumentType.fromZigType - u32" {
    const t = ArgumentType.fromZigType(u32);
    try testing.expectEqual(.u32, t);
}

test "ArgumentType.fromZigType - string" {
    const t = ArgumentType.fromZigType([]const u8);
    try testing.expectEqual(.string, t);
}

test "ArgumentType.fromZigType - optional unwraps" {
    const t = ArgumentType.fromZigType(?u32);
    try testing.expectEqual(.u32, t);
}

test "ArgumentType.matches - same types match" {
    const t1 = ArgumentType.u32;
    const t2 = ArgumentType.u32;
    try testing.expect(t1.matches(t2));
}

test "ArgumentType.matches - different types don't match" {
    const t1 = ArgumentType.u32;
    const t2 = ArgumentType.bool;
    try testing.expect(!t1.matches(t2));
}

Step 2: Implement ArgumentType (1.5 hours)

File: src/ArgumentType.zig

const std = @import("std");

pub const ArgumentType = enum {
    bool,
    u8, u16, u32, u64,
    i8, i16, i32, i64,
    string,
    string_list,
    enum_type,

    /// Convert a Zig type to ArgumentType at compile time
    pub fn fromZigType(comptime T: type) ArgumentType {
        const info = @typeInfo(T);
        
        return switch (info) {
            .Bool => .bool,
            
            .Int => |int| {
                if (int.signedness == .unsigned) {
                    return switch (int.bits) {
                        8 => .u8,
                        16 => .u16,
                        32 => .u32,
                        64 => .u64,
                        else => @compileError("Unsupported unsigned int size: " ++ 
                                             @typeName(T)),
                    };
                } else {
                    return switch (int.bits) {
                        8 => .i8,
                        16 => .i16,
                        32 => .i32,
                        64 => .i64,
                        else => @compileError("Unsupported signed int size: " ++ 
                                             @typeName(T)),
                    };
                }
            },
            
            .Pointer => |ptr| {
                if (ptr.size == .Slice) {
                    if (ptr.child == u8) return .string;
                    
                    // Check for []const []const u8 (string list)
                    const child_info = @typeInfo(ptr.child);
                    if (child_info == .Pointer) {
                        const inner_ptr = child_info.Pointer;
                        if (inner_ptr.size == .Slice and inner_ptr.child == u8) {
                            return .string_list;
                        }
                    }
                }
                
                @compileError("Unsupported pointer type: " ++ @typeName(T));
            },
            
            .Enum => .enum_type,
            
            .Optional => |opt| fromZigType(opt.child),
            
            else => @compileError("Unsupported argument type: " ++ @typeName(T)),
        };
    }

    /// Check if two ArgumentTypes are compatible
    pub fn matches(self: ArgumentType, other: ArgumentType) bool {
        return self == other;
    }
};

// Compile-time tests
comptime {
    _ = ArgumentType.fromZigType(bool);
    _ = ArgumentType.fromZigType(u32);
    _ = ArgumentType.fromZigType([]const u8);
    _ = ArgumentType.fromZigType(?u32);
}

Step 3: Run Tests (5 minutes)

zig build test
# Should pass all tests

Step 4: Update build.zig for ArgumentType (5 minutes)

Add ArgumentType to the tests:

tests.root_module.addAnonymousImport("ArgumentType", .{
    .root_source_file = b.path("src/ArgumentType.zig"),
});

Day 1 Success Criteria ✓

At end of Day 1, you should have:

  • Project structure created
  • build.zig working
  • ArgumentType fully implemented
  • All type detection tests passing
  • Comptime tests verifying common types

Progress: ~10% complete, on track!


Day 2 Morning: ParsedValue (Phase 1.2)

Step 1: Write Tests

Add to tests/type_test.zig:

const ParsedValue = @import("ArgumentType.zig").ParsedValue;

test "ParsedValue.fromString - bool true" {
    const allocator = testing.allocator;
    const pv = try ParsedValue.fromString(.bool, "true", allocator);
    defer pv.deinit(allocator);
    try testing.expectEqual(true, pv.bool_val);
}

test "ParsedValue.fromString - u32" {
    const allocator = testing.allocator;
    const pv = try ParsedValue.fromString(.u32, "42", allocator);
    defer pv.deinit(allocator);
    try testing.expectEqual(@as(u32, 42), pv.u32_val);
}

test "ParsedValue.toTypedValue - u32" {
    const allocator = testing.allocator;
    const pv = try ParsedValue.fromString(.u32, "42", allocator);
    defer pv.deinit(allocator);
    const val = pv.toTypedValue(u32);
    try testing.expectEqual(@as(u32, 42), val);
}

Step 2: Implement ParsedValue

Add to src/ArgumentType.zig:

pub const ParsedValue = union(ArgumentType) {
    bool: bool,
    u8: u8, u16: u16, u32: u32, u64: u64,
    i8: i8, i16: i16, i32: i32, i64: i64,
    string: []const u8,
    string_list: []const []const u8,
    enum_type: []const u8,

    pub fn fromString(
        arg_type: ArgumentType,
        s: []const u8,
        allocator: std.mem.Allocator,
    ) !ParsedValue {
        return switch (arg_type) {
            .bool => .{ .bool = try parseBool(s) },
            .u8 => .{ .u8 = try std.fmt.parseInt(u8, s, 10) },
            .u16 => .{ .u16 = try std.fmt.parseInt(u16, s, 10) },
            .u32 => .{ .u32 = try std.fmt.parseInt(u32, s, 10) },
            .u64 => .{ .u64 = try std.fmt.parseInt(u64, s, 10) },
            .i8 => .{ .i8 = try std.fmt.parseInt(i8, s, 10) },
            .i16 => .{ .i16 = try std.fmt.parseInt(i16, s, 10) },
            .i32 => .{ .i32 = try std.fmt.parseInt(i32, s, 10) },
            .i64 => .{ .i64 = try std.fmt.parseInt(i64, s, 10) },
            .string => .{ .string = try allocator.dupe(u8, s) },
            .string_list => .{ .string_list = try parseList(s, allocator) },
            .enum_type => .{ .enum_type = try allocator.dupe(u8, s) },
        };
    }

    pub fn toTypedValue(self: ParsedValue, comptime T: type) T {
        const arg_type = ArgumentType.fromZigType(T);
        return switch (arg_type) {
            .bool => self.bool,
            .u8 => self.u8,
            .u16 => self.u16,
            .u32 => self.u32,
            .u64 => self.u64,
            .i8 => self.i8,
            .i16 => self.i16,
            .i32 => self.i32,
            .i64 => self.i64,
            .string => self.string,
            .string_list => self.string_list,
            .enum_type => {
                // For enums, need to convert string to enum at runtime
                // This is a placeholder - full implementation in Phase 4
                @compileError("Enum conversion not yet implemented");
            },
        };
    }

    pub fn deinit(self: ParsedValue, allocator: std.mem.Allocator) void {
        switch (self) {
            .string => |s| allocator.free(s),
            .string_list => |list| {
                for (list) |item| allocator.free(item);
                allocator.free(list);
            },
            .enum_type => |s| allocator.free(s),
            else => {},
        }
    }
};

fn parseBool(s: []const u8) !bool {
    if (std.mem.eql(u8, s, "true") or std.mem.eql(u8, s, "1") or 
        std.mem.eql(u8, s, "yes")) {
        return true;
    } else if (std.mem.eql(u8, s, "false") or std.mem.eql(u8, s, "0") or
               std.mem.eql(u8, s, "no")) {
        return false;
    }
    return error.InvalidBooleanValue;
}

fn parseList(s: []const u8, allocator: std.mem.Allocator) ![]const []const u8 {
    var list = std.ArrayList([]const u8).init(allocator);
    errdefer {
        for (list.items) |item| allocator.free(item);
        list.deinit();
    }

    var iter = std.mem.splitScalar(u8, s, ',');
    while (iter.next()) |item| {
        const trimmed = std.mem.trim(u8, item, " \t");
        try list.append(try allocator.dupe(u8, trimmed));
    }

    return try list.toOwnedSlice();
}

Step 3: Run Tests

zig build test

Momentum Tips

Keep Moving Forward:

  1. If stuck > 30 minutes: Skip to next task, come back later
  2. If test fails: Debug immediately, don't move on
  3. If design unclear: Implement simplest version, refactor later
  4. Commit often: After each green test

Daily Review (15 minutes EOD):

  • What did I accomplish?
  • What's blocking me?
  • What's tomorrow's priority?

Weekly Review (30 minutes Friday):

  • Am I on schedule?
  • Do I need to adjust the plan?
  • What did I learn?

Common Issues and Solutions

Issue: Comptime too complex

Solution: Move to runtime, optimize later

Issue: Memory leaks in tests

Solution: Add defer immediately after allocation

Issue: Type conversion not working

Solution: Check ArgumentType.fromZigType() logic

Issue: Tests not compiling

Solution: Check imports and build.zig configuration


Morale Boosters

  • Each passing test is progress!
  • Small commits compound into big features
  • Taking breaks prevents burnout
  • Asking for help is strength, not weakness
  • Perfect is the enemy of done - ship it!

You've got this! 💪


Contact/Support

  • Review design docs in research/ when unsure
  • Check todo/implementation_plan_v2.md for detailed steps
  • Run zig build test frequently
  • Trust the process - you planned well!

START WITH DAY 1 MORNING. BUILD INCREMENTALLY. TEST EVERYTHING. 🚀