diff --git a/.envrc b/.envrc index 680227f..9a7514f 100644 --- a/.envrc +++ b/.envrc @@ -1,3 +1,4 @@ use flake -export PATH=".:/home/spencer/github.com/envr-zig/deps/zig:/home/spencer/github.com/envr-zig/deps/zls:$PATH" +ROOT="/home/spencer/Desktop/envr" +export PATH=".:${ROOT}/deps/zig:${ROOT}/deps/zls:$PATH" diff --git a/.gitignore b/.gitignore index 8461b76..8c93171 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ # dev env .direnv +/.env # dependencies deps diff --git a/src/Db.zig b/src/Db.zig index c297deb..1b831a0 100644 --- a/src/Db.zig +++ b/src/Db.zig @@ -46,11 +46,27 @@ pub fn open( defer private_keys.deinit(gpa); for (opts.config.keys) |key| { - private_keys.appendAssumeCapacity(key.private); + // FIXME: cheating here + if (std.mem.startsWith(u8, key.private, "~/")) { + const key_path = try std.fs.path.join(gpa, &.{ + opts.home, + key.private[2..], + }); + private_keys.appendAssumeCapacity(key_path); + // defer gpa.free(key_path); + } else { + private_keys.appendAssumeCapacity(key.private); + } } // TODO: Pass key(s) from Config try age.decrypt(io, gpa, private_keys.items, db_path, tmp_db_path); + + for (opts.config.keys, 0..) |key, i| { + if (std.mem.startsWith(u8, key.private, "~/")) { + gpa.free(private_keys.items[i]); + } + } } } @@ -173,7 +189,7 @@ const EnvFile = struct { sha256: []const u8, contents: []const u8, - fn deinit(self: *EnvFile, alloc: std.mem.Allocator) void { + pub fn deinit(self: *EnvFile, alloc: std.mem.Allocator) void { alloc.free(self.path); alloc.free(self.remotes); alloc.free(self.sha256); diff --git a/src/root.zig b/src/root.zig index 9a196c4..70daf3b 100644 --- a/src/root.zig +++ b/src/root.zig @@ -2,11 +2,11 @@ const std = @import("std"); const Io = std.Io; -const comma = @import("comma"); -const Command = comma.Command; +const Command = @import("comma").Command; const Config = @import("Config.zig"); const Db = @import("Db.zig"); +const tabula = @import("./tabula.zig"); pub const root: Command = .new(.{ .name = "envr", @@ -80,15 +80,18 @@ pub fn list( .tmp = tmp, }); - _ = try out.write("Path\n"); const files = try db.list(arena); - for (files) |file| { - // TODO: Table printer - try out.print("{s}\n", .{file.path}); - } + defer arena.free(files); + + try tabula.structs(@TypeOf(files[0]), files, out); try out.flush(); - return db.close(io, arena); // TODO: Defer this + try db.close(io, arena); // TODO: Defer this + + // TODO: Is this bad? + for (files) |file| { + @constCast(&file).deinit(arena); + } } test { @@ -129,3 +132,80 @@ test "parse unknown" { try std.testing.expectEqual(.unknown, cmd); } + +test "list returns a table" { + const io = std.testing.io; + const gpa = std.testing.allocator; + + var tmp_dir = std.testing.tmpDir(.{}); + defer tmp_dir.cleanup(); + + try tmp_dir.dir.createDir(io, "home", .default_dir); + try tmp_dir.dir.createDir(io, "home/.envr", .default_dir); + try tmp_dir.dir.createDir(io, "home/.ssh", .default_dir); + try tmp_dir.dir.createDir(io, "tmp", .default_dir); + + const tmp_dir_path = try tmp_dir.dir.realPathFileAlloc(io, ".", gpa); + defer gpa.free(tmp_dir_path); + + const home = try std.fs.path.join(gpa, &.{ tmp_dir_path, "home" }); + defer gpa.free(home); + const tmp = try std.fs.path.join(gpa, &.{ tmp_dir_path, "tmp" }); + defer gpa.free(tmp); + + try std.Io.Dir.cwd().copyFile( + "fixtures/encrypted-single-file.db.age", + tmp_dir.dir, + "home/.envr/data.age", + io, + .{}, + ); + + try std.Io.Dir.cwd().copyFile( + "fixtures/default_config.json", + tmp_dir.dir, + "home/.envr/config.json", + io, + .{}, + ); + + try std.Io.Dir.cwd().copyFile( + "fixtures/insecure-test-key", + tmp_dir.dir, + "home/.ssh/id_ed25519", + io, + .{}, + ); + + try std.Io.Dir.cwd().copyFile( + "fixtures/insecure-test-key.pub", + tmp_dir.dir, + "home/.ssh/id_ed25519.pub", + io, + .{}, + ); + + var out: std.Io.Writer.Allocating = .init(gpa); + defer out.deinit(); + + // Run Test + + try list( + io, + std.testing.allocator, + &out.writer, + home, + tmp, + ); + + try std.testing.expectEqualStrings( + \\Path + \\~/project/.env.example + \\ + // \\┌─────────────────────────────┬──────┐ + // \\│ DIRECTORY │ PATH │ + // \\├─────────────────────────────┼──────┤ + // \\│ /home/spencer/Desktop/envr/ │ .env │ + // \\└─────────────────────────────┴──────┘ + , try out.toOwnedSlice()); +} diff --git a/src/tabula.zig b/src/tabula.zig new file mode 100644 index 0000000..5a13dc4 --- /dev/null +++ b/src/tabula.zig @@ -0,0 +1,26 @@ +const std = @import("std"); + +const sep = "|"; + +pub fn structs(comptime T: type, items: []T, out: *std.Io.Writer) !void { + // Print Header + { + _ = try out.write(sep); + + const fields = @typeInfo(T).@"struct".fields; + inline for (fields) |field| { + try out.print(" {s} {s}", .{ field.name, sep }); + } + + try out.print("\n", .{}); + } + + // Print body + for (items) |item| { + try out.print("{s}\n", .{item.path}); + } +} + +// TODO: fn determine_line_lengths(T: type, items: []T) + +// TODO: test "returns a table" {}