const std = @import("std");
const vault_mod = @import("vault.zig");
const Vault = vault_mod.Vault;
const RunError = error{
MissingMasterPassword,
UnknownCommand,
WriteFailed,
} || std.mem.Allocator.Error || vault_mod.CreateError || vault_mod.OpenError;
const Command = enum {
init,
open,
unknown,
fn parse(raw: []const u8) Command {
if (std.mem.eql(u8, raw, "init")) return .init;
if (std.mem.eql(u8, raw, "open")) return .open;
return .unknown;
}
};
fn usage(out: *std.Io.Writer) !void {
try out.writeAll("TODO: ask claude to generate this");
}
fn hasFlag(args: []const [:0]const u8, flag: []const u8) bool {
for (args[1..]) |arg| {
if (std.mem.eql(u8, arg, flag)) return true;
}
return false;
}
fn valueOfFrom(args: []const [:0]const u8, start_index: usize, flag: []const u8) ?[]const u8 {
var i = start_index;
while (i < args.len) : (i += 1) {
if (std.mem.eql(u8, args[i], flag)) {
if (i + 1 == args.len) return null;
return args[i + 1];
}
}
return null;
}
fn passwordOption(args: []const [:0]const u8, start_index: usize) ?[]const u8 {
return valueOfFrom(args, start_index, "-p") orelse valueOfFrom(args, start_index, "--password");
}
fn run(allocator: std.mem.Allocator, io: std.Io, out: *std.Io.Writer, args: []const [:0]const u8) RunError!void {
if (args.len < 2 or hasFlag(args, "--help")) {
try usage(out);
return;
}
const command = Command.parse(args[1]);
switch (command) {
.init => {
const raw_password = passwordOption(args, 2) orelse return error.MissingMasterPassword;
var vault = Vault.init(allocator, io, raw_password) catch |err| switch (err) {
error.VaultAlreadyExists => {
try out.print("`{s}` has already been initialized.\n", .{"pak.db"});
return;
},
else => return err,
};
defer vault.close();
// TODO: make path configurable
try out.print("initialized vault at `{s}`\n", .{"pak.db"});
},
.open => {
const raw_password = passwordOption(args, 2) orelse return error.MissingMasterPassword;
var vault = Vault.open(allocator, io, raw_password) catch |err| switch (err) {
error.InvalidMasterPassword => {
try out.writeAll("verifier: failed\n");
return;
},
else => return err,
};
defer vault.close();
try out.writeAll("verifier: ok\nopened\n");
},
.unknown => return error.UnknownCommand,
}
}
pub fn main(init: std.process.Init) !void {
const allocator = init.arena.allocator();
const args = try init.minimal.args.toSlice(allocator);
var stdout_buffer: [4096]u8 = undefined;
var stdout_writer = std.Io.File.stdout().writer(init.io, &stdout_buffer);
const out = &stdout_writer.interface;
defer out.flush() catch {};
run(allocator, init.io, out, args) catch |err| switch (err) {
error.MissingMasterPassword => try out.writeAll("error: missing master password\n"),
error.UnknownCommand => try out.writeAll("error: unknown command\n"),
else => return err,
};
}
test "init password accepts short and long password flags" {
const short_args = [_][:0]const u8{ "pak", "init", "-p", "secret" };
try std.testing.expectEqualStrings("secret", passwordOption(&short_args, 2).?);
const long_args = [_][:0]const u8{ "pak", "init", "--password", "secret" };
try std.testing.expectEqualStrings("secret", passwordOption(&long_args, 2).?);
}
test "command parser recognizes open command" {
try std.testing.expectEqual(Command.init, Command.parse("init").?);
try std.testing.expectEqual(Command.open, Command.parse("open").?);
try std.testing.expectEqual(@as(?Command, null), Command.parse("unknown"));
}