diff options
| author | Gabriel Schneider <[email protected]> | 2026-07-02 21:41:13 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-08-01 15:02:07 -0300 |
| commit | 81992cc92743a98066ee1a9025bc74f91cf0e967 (patch) | |
| tree | 12c07f68f8ad030f6c6f8a2b3415998d0a87eee7 /dump.zig | |
| parent | bead9f0b78380d1428f40fdab339c1c8f88fa626 (diff) | |
| download | pardes-81992cc92743a98066ee1a9025bc74f91cf0e967.tar.gz pardes-81992cc92743a98066ee1a9025bc74f91cf0e967.zip | |
storage and replay
Diffstat (limited to 'dump.zig')
| -rw-r--r-- | dump.zig | 200 |
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); + } +} |
