summaryrefslogtreecommitdiff
path: root/dump.zig
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-07-02 21:41:13 -0300
committerGabriel Schneider <[email protected]>2026-08-01 15:02:07 -0300
commit81992cc92743a98066ee1a9025bc74f91cf0e967 (patch)
tree12c07f68f8ad030f6c6f8a2b3415998d0a87eee7 /dump.zig
parentbead9f0b78380d1428f40fdab339c1c8f88fa626 (diff)
downloadpardes-81992cc92743a98066ee1a9025bc74f91cf0e967.tar.gz
pardes-81992cc92743a98066ee1a9025bc74f91cf0e967.zip
storage and replay
Diffstat (limited to 'dump.zig')
-rw-r--r--dump.zig200
1 files changed, 200 insertions, 0 deletions
diff --git a/dump.zig b/dump.zig
new file mode 100644
index 00000000..5a55f04f
--- /dev/null
+++ b/dump.zig
@@ -0,0 +1,200 @@
+const std = @import("std");
+
+pub const default_path = "/tmp/pardes-dump.zon";
+pub const magic = "pardes-dump";
+pub const version: u32 = 1;
+pub const max_panes: usize = 16;
+pub const max_cols: usize = 6;
+
+pub const Size = struct {
+ cols: u16,
+ rows: u16,
+};
+
+pub const Cursor = struct {
+ col: u16 = 0,
+ row: u16 = 0,
+};
+
+pub const PaneKind = enum {
+ terminal,
+ file,
+ image,
+};
+
+pub const Terminal = struct {
+ cwd: []const u8 = "",
+ stream: []const u8 = "",
+ stream_b64: []const u8 = "",
+ cursor: Cursor = .{},
+};
+
+pub const File = struct {
+ path: []const u8 = "",
+ content: []const u8 = "",
+ content_b64: []const u8 = "",
+};
+
+pub const Image = struct {
+ path: []const u8 = "",
+ bytes_b64: []const u8 = "",
+};
+
+pub const Pane = struct {
+ kind: PaneKind,
+ tag: []const u8,
+ body: []const u8,
+ scroll: usize = 0,
+ cols: u16 = 0,
+ rows: u16 = 0,
+ vweight: f32 = 1,
+ terminal: ?Terminal = null,
+ file: ?File = null,
+ image: ?Image = null,
+};
+
+pub const Column = struct {
+ weight: f32 = 1,
+ panes: []const usize = &.{},
+};
+
+pub const State = struct {
+ magic: []const u8 = magic,
+ version: u32 = version,
+ screen: Size,
+ active: usize = 0,
+ topbar: []const u8 = "",
+ theme: []const u8 = "dark",
+ columns: []const Column = &.{},
+ panes: []const Pane = &.{},
+};
+
+pub fn validate(state: State) !void {
+ if (!std.mem.eql(u8, state.magic, magic)) return error.BadDumpMagic;
+ if (state.version != version) return error.BadDumpVersion;
+ if (state.panes.len == 0 or state.panes.len > max_panes) return error.BadDumpPanes;
+ if (state.columns.len == 0 or state.columns.len > max_cols) return error.BadDumpColumns;
+ if (state.active >= state.panes.len) return error.BadDumpActive;
+ for (state.columns) |col| {
+ if (col.panes.len == 0 or col.panes.len > max_panes) return error.BadDumpColumns;
+ for (col.panes) |pane| {
+ if (pane >= state.panes.len) return error.BadDumpPaneRef;
+ }
+ }
+ for (state.panes) |pane| {
+ switch (pane.kind) {
+ .terminal => if (pane.terminal == null) return error.BadDumpPaneKind,
+ .file => if (pane.file == null) return error.BadDumpPaneKind,
+ .image => if (pane.image == null) return error.BadDumpPaneKind,
+ }
+ }
+}
+
+pub fn writeFile(io: std.Io, gpa: std.mem.Allocator, path: []const u8, state: State) !void {
+ try validate(state);
+ var out: std.Io.Writer.Allocating = .init(gpa);
+ defer out.deinit();
+ try std.zon.stringify.serialize(state, .{ .whitespace = true }, &out.writer);
+
+ var file = try std.Io.Dir.createFileAbsolute(io, path, .{});
+ defer file.close(io);
+ try file.writeStreamingAll(io, out.written());
+}
+
+pub fn readFile(io: std.Io, gpa: std.mem.Allocator, path: []const u8) !State {
+ const bytes = try std.Io.Dir.cwd().readFileAlloc(io, path, gpa, .limited(64 * 1024 * 1024));
+ defer gpa.free(bytes);
+ const source = try gpa.dupeZ(u8, bytes);
+ defer gpa.free(source);
+
+ var diag: std.zon.parse.Diagnostics = .{};
+ defer diag.deinit(gpa);
+ const state = std.zon.parse.fromSliceAlloc(State, gpa, source, &diag, .{}) catch |err| {
+ std.log.err("parse dump {s}: {f}", .{ path, diag });
+ return err;
+ };
+ errdefer std.zon.parse.free(gpa, state);
+ try validate(state);
+ return state;
+}
+
+pub fn free(gpa: std.mem.Allocator, state: State) void {
+ std.zon.parse.free(gpa, state);
+}
+
+pub fn encodeBytes(alloc: std.mem.Allocator, bytes: []const u8) ![]const u8 {
+ const enc = std.base64.standard.Encoder;
+ const out = try alloc.alloc(u8, enc.calcSize(bytes.len));
+ return enc.encode(out, bytes);
+}
+
+pub fn decodeBytes(alloc: std.mem.Allocator, b64: []const u8) ![]u8 {
+ const dec = std.base64.standard.Decoder;
+ const n = try dec.calcSizeForSlice(b64);
+ const out = try alloc.alloc(u8, n);
+ errdefer alloc.free(out);
+ try dec.decode(out, b64);
+ return out;
+}
+
+test "dump zon roundtrip" {
+ const gpa = std.testing.allocator;
+ const tty_b64 = try encodeBytes(gpa, "old\nhello\nworld");
+ defer gpa.free(tty_b64);
+ const file_b64 = try encodeBytes(gpa, "alpha\nbeta\n");
+ defer gpa.free(file_b64);
+ const panes = [_]Pane{
+ .{
+ .kind = .terminal,
+ .tag = "nm /tmp Del",
+ .body = "hello\nworld",
+ .scroll = 1,
+ .cols = 40,
+ .rows = 10,
+ .terminal = .{ .cwd = "/tmp", .stream = "old\nhello\nworld", .stream_b64 = tty_b64, .cursor = .{ .col = 3, .row = 2 } },
+ },
+ .{
+ .kind = .file,
+ .tag = "nm /tmp/a.txt Save Del",
+ .body = " 1 alpha",
+ .cols = 20,
+ .rows = 5,
+ .file = .{ .path = "/tmp/a.txt", .content = "alpha\nbeta\n", .content_b64 = file_b64 },
+ },
+ };
+ const col_panes = [_]usize{ 0, 1 };
+ const columns = [_]Column{.{ .weight = 1, .panes = &col_panes }};
+ const state: State = .{
+ .screen = .{ .cols = 80, .rows = 24 },
+ .topbar = "Kill Newcol Dump",
+ .theme = "dark",
+ .columns = &columns,
+ .panes = &panes,
+ };
+
+ var out: std.Io.Writer.Allocating = .init(gpa);
+ defer out.deinit();
+ try std.zon.stringify.serialize(state, .{ .whitespace = true }, &out.writer);
+ const source = try out.toOwnedSliceSentinel(0);
+ defer gpa.free(source);
+
+ var diag: std.zon.parse.Diagnostics = .{};
+ defer diag.deinit(gpa);
+ const parsed = try std.zon.parse.fromSliceAlloc(State, gpa, source, &diag, .{});
+ defer std.zon.parse.free(gpa, parsed);
+ try validate(parsed);
+ try std.testing.expectEqual(@as(usize, 2), parsed.panes.len);
+ try std.testing.expectEqualStrings("dark", parsed.theme);
+ try std.testing.expectEqualStrings("old\nhello\nworld", parsed.panes[0].terminal.?.stream);
+ try std.testing.expectEqualStrings("alpha\nbeta\n", parsed.panes[1].file.?.content);
+ {
+ const bytes = try decodeBytes(gpa, parsed.panes[0].terminal.?.stream_b64);
+ defer gpa.free(bytes);
+ try std.testing.expectEqualStrings("old\nhello\nworld", bytes);
+ }
+ {
+ const bytes = try decodeBytes(gpa, parsed.panes[1].file.?.content_b64);
+ defer gpa.free(bytes);
+ try std.testing.expectEqualStrings("alpha\nbeta\n", bytes);
+ }
+}