mirror of
https://github.com/sbrow/envr.git
synced 2026-06-27 18:48:33 -04:00
Compare commits
3 Commits
2043e20b88
...
b3ebf7cadb
| Author | SHA1 | Date | |
|---|---|---|---|
| b3ebf7cadb | |||
| 6acd1f9d53 | |||
| 681931fb3b |
3
.envrc
3
.envrc
@@ -1,3 +1,4 @@
|
|||||||
use flake
|
use flake
|
||||||
|
|
||||||
export PATH=".:/home/spencer/github.com/envr-zig/deps/zig:/home/spencer/github.com/envr-zig/deps/zls:$PATH"
|
ROOT="."
|
||||||
|
export PATH=".:${ROOT}/deps/zig:${ROOT}/deps/zls:$PATH"
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,5 +1,6 @@
|
|||||||
# dev env
|
# dev env
|
||||||
.direnv
|
.direnv
|
||||||
|
/.env
|
||||||
|
|
||||||
# dependencies
|
# dependencies
|
||||||
deps
|
deps
|
||||||
|
|||||||
20
src/Db.zig
20
src/Db.zig
@@ -46,11 +46,27 @@ pub fn open(
|
|||||||
defer private_keys.deinit(gpa);
|
defer private_keys.deinit(gpa);
|
||||||
|
|
||||||
for (opts.config.keys) |key| {
|
for (opts.config.keys) |key| {
|
||||||
|
// 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);
|
private_keys.appendAssumeCapacity(key.private);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Pass key(s) from Config
|
// TODO: Pass key(s) from Config
|
||||||
try age.decrypt(io, gpa, private_keys.items, db_path, tmp_db_path);
|
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]);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,7 +177,7 @@ pub fn list(self: *@This(), gpa: std.mem.Allocator) ![]EnvFile {
|
|||||||
return stmt.all(EnvFile, gpa, .{}, .{});
|
return stmt.all(EnvFile, gpa, .{}, .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
const EnvFile = struct {
|
pub const EnvFile = struct {
|
||||||
// TODO: Should use file_name in the struct and derive from the path.
|
// TODO: Should use file_name in the struct and derive from the path.
|
||||||
path: []const u8,
|
path: []const u8,
|
||||||
|
|
||||||
@@ -173,7 +189,7 @@ const EnvFile = struct {
|
|||||||
sha256: []const u8,
|
sha256: []const u8,
|
||||||
contents: []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.path);
|
||||||
alloc.free(self.remotes);
|
alloc.free(self.remotes);
|
||||||
alloc.free(self.sha256);
|
alloc.free(self.sha256);
|
||||||
|
|||||||
59
src/main.zig
59
src/main.zig
@@ -44,7 +44,7 @@ fn run(
|
|||||||
var stdout_file_writer: Io.File.Writer = .init(.stdout(), io, &stdout_buffer);
|
var stdout_file_writer: Io.File.Writer = .init(.stdout(), io, &stdout_buffer);
|
||||||
const stdout_writer = &stdout_file_writer.interface;
|
const stdout_writer = &stdout_file_writer.interface;
|
||||||
|
|
||||||
return deps(
|
return envr.deps(
|
||||||
io,
|
io,
|
||||||
stdout_writer,
|
stdout_writer,
|
||||||
environ_map.get("PATH").?,
|
environ_map.get("PATH").?,
|
||||||
@@ -75,63 +75,6 @@ fn version(writer: *Io.Writer) !void {
|
|||||||
try writer.flush();
|
try writer.flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Display dependency statuses
|
|
||||||
fn deps(
|
|
||||||
io: Io,
|
|
||||||
writer: *Io.Writer,
|
|
||||||
path: []const u8,
|
|
||||||
) !void {
|
|
||||||
const feats: Features = try .scan(io, path);
|
|
||||||
|
|
||||||
// FIXME: Draw as a table
|
|
||||||
try writer.print("features: {}", .{feats});
|
|
||||||
try writer.flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
const Features = packed struct {
|
|
||||||
git: bool = false,
|
|
||||||
fd: bool = false,
|
|
||||||
const all_features: Features = .{
|
|
||||||
.git = true,
|
|
||||||
.fd = true,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Scans your PATH variable for programs.
|
|
||||||
pub fn scan(io: Io, path: []const u8) !@This() {
|
|
||||||
var feats: Features = .{};
|
|
||||||
|
|
||||||
var dirs = std.mem.splitScalar(u8, path, std.fs.path.delimiter);
|
|
||||||
|
|
||||||
loop: while (dirs.next()) |dir| {
|
|
||||||
const dirt = Io.Dir.openDir(Io.Dir.cwd(), io, dir, .{ .follow_symlinks = true, .iterate = true }) catch continue;
|
|
||||||
defer dirt.close(io);
|
|
||||||
|
|
||||||
var dir_paths = dirt.iterate();
|
|
||||||
|
|
||||||
while (try dir_paths.next(io)) |file| {
|
|
||||||
// FIXME: Check if executable
|
|
||||||
if (std.mem.eql(u8, std.fs.path.basename(file.name), "git")) {
|
|
||||||
feats.git = true;
|
|
||||||
|
|
||||||
if (feats == Features.all_features) {
|
|
||||||
break :loop;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (std.mem.eql(u8, std.fs.path.basename(file.name), "fd")) {
|
|
||||||
feats.fd = true;
|
|
||||||
|
|
||||||
if (feats == Features.all_features) {
|
|
||||||
break :loop;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return feats;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
fn fallback_to_go(
|
fn fallback_to_go(
|
||||||
io: Io,
|
io: Io,
|
||||||
arena: std.mem.Allocator,
|
arena: std.mem.Allocator,
|
||||||
|
|||||||
161
src/root.zig
161
src/root.zig
@@ -2,11 +2,11 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const Io = std.Io;
|
const Io = std.Io;
|
||||||
|
|
||||||
const comma = @import("comma");
|
const Command = @import("comma").Command;
|
||||||
const Command = comma.Command;
|
|
||||||
|
|
||||||
const Config = @import("Config.zig");
|
const Config = @import("Config.zig");
|
||||||
const Db = @import("Db.zig");
|
const Db = @import("Db.zig");
|
||||||
|
const tabula = @import("./tabula.zig");
|
||||||
|
|
||||||
pub const root: Command = .new(.{
|
pub const root: Command = .new(.{
|
||||||
.name = "envr",
|
.name = "envr",
|
||||||
@@ -63,6 +63,63 @@ pub const root: Command = .new(.{
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Display dependency statuses
|
||||||
|
pub fn deps(
|
||||||
|
io: Io,
|
||||||
|
writer: *Io.Writer,
|
||||||
|
path: []const u8,
|
||||||
|
) !void {
|
||||||
|
const feats: Features = try .scan(io, path);
|
||||||
|
|
||||||
|
// FIXME: Draw as a table
|
||||||
|
try writer.print("features: {}", .{feats});
|
||||||
|
try writer.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
const Features = packed struct {
|
||||||
|
git: bool = false,
|
||||||
|
fd: bool = false,
|
||||||
|
const all_features: Features = .{
|
||||||
|
.git = true,
|
||||||
|
.fd = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Scans your PATH variable for programs.
|
||||||
|
pub fn scan(io: Io, path: []const u8) !@This() {
|
||||||
|
var feats: Features = .{};
|
||||||
|
|
||||||
|
var dirs = std.mem.splitScalar(u8, path, std.fs.path.delimiter);
|
||||||
|
|
||||||
|
loop: while (dirs.next()) |dir| {
|
||||||
|
const dirt = Io.Dir.openDir(Io.Dir.cwd(), io, dir, .{ .follow_symlinks = true, .iterate = true }) catch continue;
|
||||||
|
defer dirt.close(io);
|
||||||
|
|
||||||
|
var dir_paths = dirt.iterate();
|
||||||
|
|
||||||
|
while (try dir_paths.next(io)) |file| {
|
||||||
|
// FIXME: Check if executable
|
||||||
|
if (std.mem.eql(u8, std.fs.path.basename(file.name), "git")) {
|
||||||
|
feats.git = true;
|
||||||
|
|
||||||
|
if (feats == Features.all_features) {
|
||||||
|
break :loop;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (std.mem.eql(u8, std.fs.path.basename(file.name), "fd")) {
|
||||||
|
feats.fd = true;
|
||||||
|
|
||||||
|
if (feats == Features.all_features) {
|
||||||
|
break :loop;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return feats;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
pub fn list(
|
pub fn list(
|
||||||
io: Io,
|
io: Io,
|
||||||
arena: std.mem.Allocator,
|
arena: std.mem.Allocator,
|
||||||
@@ -72,23 +129,29 @@ pub fn list(
|
|||||||
) !void {
|
) !void {
|
||||||
// TODO: Don't hardcode
|
// TODO: Don't hardcode
|
||||||
const cfgPath = try std.fs.path.join(arena, &.{ home, ".envr", "config.json" });
|
const cfgPath = try std.fs.path.join(arena, &.{ home, ".envr", "config.json" });
|
||||||
const cfg: Config = (try Config.load(io, arena, cfgPath)).value;
|
defer arena.free(cfgPath);
|
||||||
|
|
||||||
|
var cfg = (try Config.load(io, arena, cfgPath));
|
||||||
|
defer cfg.deinit();
|
||||||
|
|
||||||
var db: Db = try .open(io, arena, .{
|
var db: Db = try .open(io, arena, .{
|
||||||
.config = cfg,
|
.config = cfg.value,
|
||||||
.home = home,
|
.home = home,
|
||||||
.tmp = tmp,
|
.tmp = tmp,
|
||||||
});
|
});
|
||||||
|
|
||||||
_ = try out.write("Path\n");
|
|
||||||
const files = try db.list(arena);
|
const files = try db.list(arena);
|
||||||
for (files) |file| {
|
defer arena.free(files);
|
||||||
// TODO: Table printer
|
|
||||||
try out.print("{s}\n", .{file.path});
|
const table: tabula.Table(Db.EnvFile, .initOne(.path)) = .{ .items = files };
|
||||||
}
|
try out.print("{f}", .{table});
|
||||||
try out.flush();
|
try out.flush();
|
||||||
|
|
||||||
return db.close(io, arena); // TODO: Defer this
|
try db.close(io, arena); // TODO: Defer this
|
||||||
|
|
||||||
|
for (files) |*file| {
|
||||||
|
file.deinit(arena);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
test {
|
test {
|
||||||
@@ -129,3 +192,81 @@ test "parse unknown" {
|
|||||||
|
|
||||||
try std.testing.expectEqual(.unknown, cmd);
|
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,
|
||||||
|
);
|
||||||
|
|
||||||
|
const got = try out.toOwnedSlice();
|
||||||
|
defer gpa.free(got);
|
||||||
|
|
||||||
|
try std.testing.expectEqualStrings(
|
||||||
|
\\┌────────────────────────┐
|
||||||
|
\\│ path │
|
||||||
|
\\├────────────────────────┤
|
||||||
|
\\│ ~/project/.env.example │
|
||||||
|
\\└────────────────────────┘
|
||||||
|
\\
|
||||||
|
, got);
|
||||||
|
}
|
||||||
|
|||||||
311
src/tabula.zig
Normal file
311
src/tabula.zig
Normal file
@@ -0,0 +1,311 @@
|
|||||||
|
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 = "┘";
|
||||||
|
|
||||||
|
/// Prepare a TUI table to be written to a writer.
|
||||||
|
pub fn Table(
|
||||||
|
comptime T: type,
|
||||||
|
comptime fields: std.EnumSet(std.meta.FieldEnum(T)),
|
||||||
|
) type {
|
||||||
|
return struct {
|
||||||
|
items: []const T,
|
||||||
|
|
||||||
|
pub fn format(self: @This(), writer: *std.Io.Writer) !void {
|
||||||
|
const max_column_widths = determine_col_widths(T, self.items);
|
||||||
|
|
||||||
|
try header(T, fields, &max_column_widths, writer);
|
||||||
|
|
||||||
|
// Print body
|
||||||
|
for (self.items) |item| {
|
||||||
|
try writer.writeAll(sep);
|
||||||
|
|
||||||
|
comptime var itr = fields.iterator();
|
||||||
|
comptime var i: usize = 0;
|
||||||
|
inline while (comptime itr.next()) |c| : (i += 1) {
|
||||||
|
try writer.writeByte(' ');
|
||||||
|
try write_aligned(writer, @field(item, @tagName(c)), max_column_widths[i], .left);
|
||||||
|
try writer.print(" {s}", .{sep});
|
||||||
|
}
|
||||||
|
|
||||||
|
try writer.writeAll("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print post-body
|
||||||
|
{
|
||||||
|
try writer.writeAll(bl);
|
||||||
|
|
||||||
|
var itr = fields.iterator();
|
||||||
|
var i: usize = 0;
|
||||||
|
while (itr.next()) |_| : (i += 1) {
|
||||||
|
if (i > 0) {
|
||||||
|
try writer.writeAll(bm);
|
||||||
|
}
|
||||||
|
|
||||||
|
const padding = max_column_widths[i] + 2;
|
||||||
|
for (0..padding) |_| {
|
||||||
|
try writer.writeAll(hor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try writer.writeAll(br ++ "\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn determine_col_widths(
|
||||||
|
T: type,
|
||||||
|
items: []const T,
|
||||||
|
) [@typeInfo(T).@"struct".fields.len]usize {
|
||||||
|
const all_fields = @typeInfo(T).@"struct".fields;
|
||||||
|
|
||||||
|
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
|
||||||
|
const value_len = @field(item, field.name).len;
|
||||||
|
max_column_widths[i] = @max(
|
||||||
|
max_column_widths[i],
|
||||||
|
field.name.len,
|
||||||
|
value_len,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return max_column_widths;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print the header of a table
|
||||||
|
fn header(
|
||||||
|
T: type,
|
||||||
|
comptime fields: std.EnumSet(std.meta.FieldEnum(T)),
|
||||||
|
max_column_widths: []const usize,
|
||||||
|
writer: *std.Io.Writer,
|
||||||
|
) !void {
|
||||||
|
|
||||||
|
// Print Pre-Header
|
||||||
|
{
|
||||||
|
try writer.writeAll(tl);
|
||||||
|
|
||||||
|
inline for (0..comptime fields.count()) |i| {
|
||||||
|
if (i > 0) {
|
||||||
|
try writer.writeAll(tm);
|
||||||
|
}
|
||||||
|
const padding = max_column_widths[i] + 2;
|
||||||
|
for (0..padding) |_| {
|
||||||
|
try writer.writeAll(hor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try writer.writeAll(tr ++ "\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main Header
|
||||||
|
{
|
||||||
|
try writer.writeAll(sep);
|
||||||
|
|
||||||
|
comptime var itr = fields.iterator();
|
||||||
|
comptime var i: usize = 0;
|
||||||
|
inline while (comptime itr.next()) |field| : (i += 1) {
|
||||||
|
try writer.writeByte(' ');
|
||||||
|
try write_aligned(
|
||||||
|
writer,
|
||||||
|
@tagName(field),
|
||||||
|
max_column_widths[i],
|
||||||
|
.center,
|
||||||
|
);
|
||||||
|
try writer.print(" {s}", .{sep});
|
||||||
|
}
|
||||||
|
|
||||||
|
try writer.writeByte('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print post-header
|
||||||
|
{
|
||||||
|
try writer.writeAll(ml);
|
||||||
|
|
||||||
|
inline for (0..comptime fields.count()) |i| {
|
||||||
|
if (i > 0) {
|
||||||
|
try writer.writeAll(mm);
|
||||||
|
}
|
||||||
|
const padding = max_column_widths[i] + 2;
|
||||||
|
for (0..padding) |_| {
|
||||||
|
try writer.writeAll(hor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try writer.writeAll(mr ++ "\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_aligned(
|
||||||
|
writer: *std.Io.Writer,
|
||||||
|
data: []const u8,
|
||||||
|
max_width: usize,
|
||||||
|
alignment: Alignment,
|
||||||
|
) !void {
|
||||||
|
std.debug.assert(data.len > 0);
|
||||||
|
std.debug.assert(max_width >= data.len);
|
||||||
|
|
||||||
|
const padding: [2]usize = switch (alignment) {
|
||||||
|
.left => .{ 0, max_width - data.len },
|
||||||
|
.right => .{ max_width - data.len, 0 },
|
||||||
|
.center => blk: {
|
||||||
|
// Faster to inline the divFloor?
|
||||||
|
const half = @divFloor(max_width - data.len, 2);
|
||||||
|
break :blk .{ half, max_width - data.len - half };
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
for (0..padding[0]) |_| {
|
||||||
|
try writer.writeByte(' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
try writer.writeAll(data);
|
||||||
|
|
||||||
|
for (0..padding[1]) |_| {
|
||||||
|
try writer.writeByte(' ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const Alignment = enum { left, center, right };
|
||||||
|
|
||||||
|
test "can print a simple table" {
|
||||||
|
const gpa = std.testing.allocator;
|
||||||
|
|
||||||
|
var out: std.Io.Writer.Allocating = .init(gpa);
|
||||||
|
defer out.deinit();
|
||||||
|
|
||||||
|
const F = struct { foo: []const u8, bar: []const u8 };
|
||||||
|
const table: Table(F, .full) = .{
|
||||||
|
.items = &.{.{ .foo = "bat", .bar = "baz" }},
|
||||||
|
};
|
||||||
|
|
||||||
|
try out.writer.print("{f}", .{table});
|
||||||
|
|
||||||
|
const got = try out.toOwnedSlice();
|
||||||
|
defer gpa.free(got);
|
||||||
|
|
||||||
|
try std.testing.expectEqualStrings(
|
||||||
|
\\┌─────┬─────┐
|
||||||
|
\\│ foo │ bar │
|
||||||
|
\\├─────┼─────┤
|
||||||
|
\\│ bat │ baz │
|
||||||
|
\\└─────┴─────┘
|
||||||
|
\\
|
||||||
|
, got);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "can print a table with varying header widths" {
|
||||||
|
const gpa = std.testing.allocator;
|
||||||
|
|
||||||
|
var out: std.Io.Writer.Allocating = .init(gpa);
|
||||||
|
defer out.deinit();
|
||||||
|
|
||||||
|
const F = struct { foo: []const u8, abart: []const u8 };
|
||||||
|
const table: Table(F, .full) = .{
|
||||||
|
.items = &.{.{ .foo = "bat", .abart = "baz" }},
|
||||||
|
};
|
||||||
|
try out.writer.print("{f}", .{table});
|
||||||
|
|
||||||
|
const got = try out.toOwnedSlice();
|
||||||
|
defer gpa.free(got);
|
||||||
|
|
||||||
|
try std.testing.expectEqualStrings(
|
||||||
|
\\┌─────┬───────┐
|
||||||
|
\\│ foo │ abart │
|
||||||
|
\\├─────┼───────┤
|
||||||
|
\\│ bat │ baz │
|
||||||
|
\\└─────┴───────┘
|
||||||
|
\\
|
||||||
|
, got);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "can print a table with varying column widths" {
|
||||||
|
const gpa = std.testing.allocator;
|
||||||
|
|
||||||
|
var out: std.Io.Writer.Allocating = .init(gpa);
|
||||||
|
defer out.deinit();
|
||||||
|
|
||||||
|
const F = struct { foo: []const u8, bar: []const u8 };
|
||||||
|
const table: Table(F, .full) = .{ .items = &.{.{ .foo = "bat", .bar = "bazzar" }} };
|
||||||
|
|
||||||
|
try out.writer.print("{f}", .{table});
|
||||||
|
|
||||||
|
const got = try out.toOwnedSlice();
|
||||||
|
defer gpa.free(got);
|
||||||
|
|
||||||
|
try std.testing.expectEqualStrings(
|
||||||
|
\\┌─────┬────────┐
|
||||||
|
\\│ foo │ bar │
|
||||||
|
\\├─────┼────────┤
|
||||||
|
\\│ bat │ bazzar │
|
||||||
|
\\└─────┴────────┘
|
||||||
|
\\
|
||||||
|
, got);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "can print a multi row table with varying column widths" {
|
||||||
|
const gpa = std.testing.allocator;
|
||||||
|
|
||||||
|
var out: std.Io.Writer.Allocating = .init(gpa);
|
||||||
|
defer out.deinit();
|
||||||
|
|
||||||
|
const F = struct { foo: []const u8, bar: []const u8 };
|
||||||
|
const table: Table(F, .full) = .{
|
||||||
|
.items = &.{
|
||||||
|
.{ .foo = "baz", .bar = "quz" },
|
||||||
|
.{ .foo = "bat", .bar = "bazzar" },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
try out.writer.print("{f}", .{table});
|
||||||
|
|
||||||
|
const got = try out.toOwnedSlice();
|
||||||
|
defer gpa.free(got);
|
||||||
|
|
||||||
|
try std.testing.expectEqualStrings(
|
||||||
|
\\┌─────┬────────┐
|
||||||
|
\\│ foo │ bar │
|
||||||
|
\\├─────┼────────┤
|
||||||
|
\\│ baz │ quz │
|
||||||
|
\\│ bat │ bazzar │
|
||||||
|
\\└─────┴────────┘
|
||||||
|
\\
|
||||||
|
, got);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "can print a table with limited columns" {
|
||||||
|
const gpa = std.testing.allocator;
|
||||||
|
|
||||||
|
var out: std.Io.Writer.Allocating = .init(gpa);
|
||||||
|
defer out.deinit();
|
||||||
|
|
||||||
|
const F = struct { foo: []const u8, bar: []const u8 };
|
||||||
|
const table: Table(F, .initOne(.foo)) = .{
|
||||||
|
.items = &.{.{ .foo = "bat", .bar = "baz" }},
|
||||||
|
};
|
||||||
|
|
||||||
|
try out.writer.print("{f}", .{table});
|
||||||
|
|
||||||
|
const got = try out.toOwnedSlice();
|
||||||
|
defer gpa.free(got);
|
||||||
|
|
||||||
|
try std.testing.expectEqualStrings(
|
||||||
|
\\┌─────┐
|
||||||
|
\\│ foo │
|
||||||
|
\\├─────┤
|
||||||
|
\\│ bat │
|
||||||
|
\\└─────┘
|
||||||
|
\\
|
||||||
|
, got);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user