400 lines
11 KiB
Markdown
400 lines
11 KiB
Markdown
# Implementation Quick Start
|
|
|
|
## Day 1 Morning: Setup
|
|
|
|
### 1. Create Directory Structure (5 minutes)
|
|
```bash
|
|
cd /home/sear/Backlog/lib/zargs
|
|
mkdir -p src tests examples
|
|
```
|
|
|
|
### 2. Create Initial Files (5 minutes)
|
|
```bash
|
|
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)
|
|
```zig
|
|
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)
|
|
```bash
|
|
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`
|
|
|
|
```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`
|
|
|
|
```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)
|
|
```bash
|
|
zig build test
|
|
# Should pass all tests
|
|
```
|
|
|
|
### Step 4: Update build.zig for ArgumentType (5 minutes)
|
|
Add ArgumentType to the tests:
|
|
```zig
|
|
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`:
|
|
|
|
```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`:
|
|
|
|
```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
|
|
```bash
|
|
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.** 🚀
|