feat: Added table viewer.

This commit is contained in:
2026-05-31 15:39:38 -04:00
parent acbda090d7
commit 64afeb99ec
5 changed files with 366 additions and 11 deletions

3
.envrc
View File

@@ -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"

1
.gitignore vendored
View File

@@ -1,5 +1,6 @@
# dev env
.direnv
/.env
# dependencies
deps

View File

@@ -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);

View File

@@ -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,23 @@ 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 = out, .fields = .initOne(.path) },
// .{ .out = 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 +137,78 @@ 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 │
\\└────────────────────────┘
\\
, try out.toOwnedSlice());
}

254
src/tabula.zig Normal file
View File

@@ -0,0 +1,254 @@
const std = @import("std");
const hor = "";
const tl = "";
const tm = "";
const tr = "";
const sep = "";
const ml = "";
const mm = "";
const mr = "";
const bl = "";
const bm = "";
const br = "";
// Print a list of structs as a table to opts.out.
pub fn structs(
comptime T: type,
items: []T,
opts: struct {
out: *std.Io.Writer, // TODO: Default stdout.
fields: std.EnumSet(std.meta.FieldEnum(T)) = .full,
},
) !void {
const writer = opts.out;
const all_fields = @typeInfo(T).@"struct".fields;
// Calculate column widths
var max_column_widths: [all_fields.len]usize = @splat(0);
{
for (items) |item| {
inline for (all_fields, 0..) |field, i| {
// TODO: Get str len of item
max_column_widths[i] = @max(max_column_widths[i], field.name.len, @field(item, field.name).len);
}
}
}
try header(T, opts.fields, &max_column_widths, opts.out);
// Print body
for (items) |item| {
_ = try writer.write(sep);
// TODO: Not the most efficient
inline for (all_fields) |field| {
var itr = opts.fields.iterator();
while (itr.next()) |c| {
if (std.mem.eql(u8, @tagName(c), field.name)) {
try writer.print(" {s} {s}", .{
@field(item, field.name),
sep,
});
break;
}
}
}
_ = try writer.write("\n");
}
// Print post-body
{
_ = try writer.write(bl);
var itr = opts.fields.iterator();
var i: usize = 0;
while (itr.next()) |_| : (i += 1) {
if (i > 0) {
_ = try writer.write(bm);
}
const padding = max_column_widths[i] + 2;
for (0..padding) |_| {
_ = try writer.write(hor);
}
}
_ = try writer.write(br);
_ = try writer.write("\n");
}
}
// Print the header of a table
fn header(
T: type,
fields: std.EnumSet(std.meta.FieldEnum(T)),
max_column_widths: []usize,
writer: *std.Io.Writer,
) !void {
// Print Pre-Header
{
_ = try writer.write(tl);
var itr = fields.iterator();
var i: usize = 0;
while (itr.next()) |_| : (i += 1) {
if (i > 0) {
_ = try writer.write(tm);
}
const padding = max_column_widths[i] + 2;
for (0..padding) |_| {
_ = try writer.write(hor);
}
}
_ = try writer.write(tr);
_ = try writer.write("\n");
}
// Main Header
{
_ = try writer.write(sep);
var itr = fields.iterator();
var i: usize = 0;
while (itr.next()) |field| : (i += 1) {
try writer.print(" {s}", .{@tagName(field)});
const padding = max_column_widths[i] - @tagName(field).len + 1;
for (0..padding) |_| {
_ = try writer.write(" ");
}
_ = try writer.write(sep);
}
try writer.print("\n", .{});
}
// Print post-header
{
_ = try writer.write(ml);
var itr = fields.iterator();
var i: usize = 0;
while (itr.next()) |_| : (i += 1) {
if (i > 0) {
_ = try writer.write(mm);
}
const padding = max_column_widths[i] + 2;
for (0..padding) |_| {
_ = try writer.write(hor);
}
}
_ = try writer.write(mr ++ "\n");
}
}
pub fn Table(T: type) type {
return struct {
rows: []const T, // TODO: Do better
// cols: []const std.builtin.Type.StructField = @typeInfo(T).@"struct".fields,
// cols: []const [:0]const u8,
cols: std.EnumSet(std.meta.FieldEnum(T)) = .full,
fn initMany(rows: []const T, cols: []const [:0]const u8) Table(T) {
return .{ .rows = rows, .cols = cols };
}
pub fn format(
self: @This(),
writer: *std.Io.Writer,
) !void {
const cols = @typeInfo(T).@"struct".fields;
// const cols = self.cols;
// Print Header
{
_ = try writer.write(sep);
inline for (cols) |col| {
var itr = self.cols.iterator();
while (itr.next()) |c| {
if (std.mem.eql(u8, @tagName(c), col.name)) {
try writer.print(" {s} {s}", .{ col.name, sep });
break;
}
}
}
try writer.print("\n", .{});
}
// Print Body {
{
for (self.rows) |row| {
_ = try writer.write(sep);
inline for (cols) |col| {
var itr = self.cols.iterator();
while (itr.next()) |c| {
if (std.mem.eql(u8, @tagName(c), col.name)) {
try writer.print(" {s} {s}", .{
@field(row, col.name),
sep,
});
}
}
}
_ = try writer.write("\n");
}
}
}
};
}
test "can print a simple table" {
const F = struct { foo: []const u8, bar: []const u8 };
// const table: Table(F) = .{ .rows = &.{.{ .foo = "bat", .bar = "baz" }} };
var out: std.Io.Writer.Allocating = .init(std.testing.allocator);
defer out.deinit();
const rows: [1]F = .{.{ .foo = "bat", .bar = "baz" }};
try structs(F, @constCast(&rows), .{ .out = &out.writer });
// try out.writer.print("{f}", .{table});
try std.testing.expectEqualStrings(
\\┌─────┬─────┐
\\│ foo │ bar │
\\├─────┼─────┤
\\│ bat │ baz │
\\└─────┴─────┘
\\
, try out.toOwnedSlice());
}
test "can print a table with limited columns" {
const F = struct { foo: []const u8, bar: []const u8 };
const rows: [1]F = .{.{ .foo = "bat", .bar = "baz" }};
// const table: Table(F) = .{ .rows = &.{.{ .foo = "bat", .bar = "baz" }}, .cols = .initOne(.foo) };
var out: std.Io.Writer.Allocating = .init(std.testing.allocator);
defer out.deinit();
// try out.writer.print("{f}", .{table});
try structs(F, @constCast(&rows), .{ .out = &out.writer, .fields = .initOne(.foo) });
try std.testing.expectEqualStrings(
\\┌─────┐
\\│ foo │
\\├─────┤
\\│ bat │
\\└─────┘
\\
, try out.toOwnedSlice());
}
// TODO: fn determine_line_lengths(T: type, items: []T)
// TODO: test "returns a table" {}