diff --git a/build.zig b/build.zig index 242a70c..4e2fbe0 100644 --- a/build.zig +++ b/build.zig @@ -14,19 +14,6 @@ pub fn build(b: *std.Build) void { b.installArtifact(exe); - const tests = b.addTest(.{ - .name = "tpb", - .root_module = b.createModule(.{ - .root_source_file = b.path("src/main.zig"), - .target = target, - .optimize = optimize, - }), - }); - - const test_step = b.step("test", "Run tests"); - const run_tests = b.addRunArtifact(tests); - test_step.dependOn(&run_tests.step); - const run_step = b.step("run", "Run the app"); const run_cmd = b.addRunArtifact(exe); diff --git a/src/Clip.zig b/src/Clip.zig index 8d264c8..7d2f6db 100644 --- a/src/Clip.zig +++ b/src/Clip.zig @@ -1,30 +1 @@ pub const OSC52 = @import("Clip/OSC52.zig"); - -// I am not sure if this is super idiomatic Zig or if this is bad practice, but -// by ensuring that all Clip backends implement the same methods, we are able -// to use the `anytype` type and we are able to have an interface that is able -// to dispatch statically - -/// Check if a function with the given name and type exists. -/// `@compileError` if not -fn assertFn(comptime T: type, comptime name: []const u8, comptime Sig: type) void { - if (!@import("std").meta.hasFn(T, name)) - @compileError(@typeName(T) ++ " missing fn " ++ name); - if (@TypeOf(@field(T, name)) != Sig) - @compileError(@typeName(T) ++ "." ++ name ++ " must be " ++ @typeName(Sig)); -} - -test { - inline for (@typeInfo(@This()).@"struct".decls) |decl| { - // Only grab types of structs - const T = @field(@This(), decl.name); - if (@TypeOf(T) != type) continue; - if (@typeInfo(T) != .@"struct") continue; - - // ============================== - // === Interface Requirements === - // ============================== - assertFn(T, "writeCopyBuffer", fn (*T, []const u8) anyerror!void); - assertFn(T, "writePasteboard", fn (*T) anyerror!void); - } -} diff --git a/src/Clip/OSC52.zig b/src/Clip/OSC52.zig index d95fca1..44d92bf 100644 --- a/src/Clip/OSC52.zig +++ b/src/Clip/OSC52.zig @@ -13,7 +13,7 @@ base64buffer: std.ArrayList(u8), leftover_bytes: [3]u8, leftover_count: u8, -const base64encoder = std.base64.Base64Encoder.init(std.base64.standard_alphabet_chars, '='); +const base64encoder = std.base64.Base64Encoder.init(std.base64.standard_alphabet_chars, null); pub fn init(alloc: std.mem.Allocator, io: Io, buf_size: usize) !OSC52 { return .{ @@ -25,34 +25,31 @@ pub fn init(alloc: std.mem.Allocator, io: Io, buf_size: usize) !OSC52 { }; } -pub fn free(self: *OSC52) void { - self.base64buffer.clearAndFree(self.alloc); -} - -pub fn writeCopyBuffer(self: *OSC52, buf: []const u8) anyerror!void { +pub fn writeCopyBuffer(self: *OSC52, buf: []u8) !void { if (buf.len == 0) return; // Start of buffer after dealing with leftover bytes const buf_idx = 3 - self.leftover_count; - // When we dont fill the leftover buffer, just copy data - if (buf_idx > buf.len) { - const end = self.leftover_count + buf.len; - @memcpy(self.leftover_bytes[self.leftover_count..end], buf[0..buf.len]); - self.leftover_count = @intCast(end); + if (buf.len + self.leftover_count < 3) { + const len = buf.len + self.leftover_count; + @memcpy(self.leftover_bytes[self.leftover_count..len], buf[0..buf.len]); + self.leftover_count = @intCast(len); return; } - @memcpy(self.leftover_bytes[self.leftover_count..3], buf[0..buf_idx]); - var temp: [5]u8 = undefined; - var s = OSC52.base64encoder.encode(&temp, &self.leftover_bytes); - try self.base64buffer.appendSlice(self.alloc, s); + if (self.leftover_count != 0) { + @memcpy(self.leftover_bytes[self.leftover_count..3], buf[0..buf_idx]); + var temp: [5]u8 = undefined; + const s = OSC52.base64encoder.encode(&temp, &self.leftover_bytes); + try self.base64buffer.appendSlice(self.alloc, s); + } const chunkable_length = buf.len - ((buf.len - buf_idx) % 3); - var chunker = std.mem.window(u8, buf[buf_idx..chunkable_length], 3, 3); + var chunker = std.mem.window(u8, buf[0..chunkable_length], 3, 3); while (chunker.next()) |chunk| { - // var temp: [5]u8 = undefined; - s = OSC52.base64encoder.encode(&temp, chunk); + var temp: [5]u8 = undefined; + const s = OSC52.base64encoder.encode(&temp, chunk); try self.base64buffer.appendSlice(self.alloc, s); } @@ -62,14 +59,7 @@ pub fn writeCopyBuffer(self: *OSC52, buf: []const u8) anyerror!void { } } -pub fn encodeRemaining(self: *OSC52) anyerror!void { - var temp: [5]u8 = undefined; - const s = OSC52.base64encoder.encode(&temp, self.leftover_bytes[0..self.leftover_count]); - try self.base64buffer.appendSlice(self.alloc, s); -} - -pub fn writePasteboard(self: *OSC52) anyerror!void { - try self.encodeRemaining(); +pub fn writePasteboard(self: *OSC52) !void { // https://ghostty.org/docs/vt/osc/52 const osc_header = &[_]u8{ @@ -86,51 +76,14 @@ pub fn writePasteboard(self: *OSC52) anyerror!void { 0x5c, // '\' }; + // Write leftover bytes + var temp: [5]u8 = undefined; + const s = OSC52.base64encoder.encode(&temp, self.leftover_bytes[0..self.leftover_count]); + try self.base64buffer.appendSlice(self.alloc, s); + // Write data to stdout const stdout = Io.File.stdout(); try stdout.writeStreamingAll(self.io, osc_header); try stdout.writeStreamingAll(self.io, self.base64buffer.items); try stdout.writeStreamingAll(self.io, osc_footer); } - -test "basic base64 encode" { - var osc = try OSC52.init(std.testing.allocator, std.testing.io, 1024); - defer osc.free(); - try osc.writeCopyBuffer("part1"); - try osc.encodeRemaining(); - try std.testing.expectEqualSlices(u8, "cGFydDE=", osc.base64buffer.items); -} - -test "multi part base64 encode" { - var osc = try OSC52.init(std.testing.allocator, std.testing.io, 1024); - defer osc.free(); - try osc.writeCopyBuffer("part1"); - try osc.writeCopyBuffer("part2"); - try osc.encodeRemaining(); - try std.testing.expectEqualSlices(u8, "cGFydDFwYXJ0Mg==", osc.base64buffer.items); -} - -test "tiny buffers base64 encode" { - var osc = try OSC52.init(std.testing.allocator, std.testing.io, 1024); - defer osc.free(); - try osc.writeCopyBuffer("part1"); - try osc.writeCopyBuffer("part2"); - try osc.writeCopyBuffer("1"); - try osc.writeCopyBuffer("2"); - try osc.writeCopyBuffer("3"); - try osc.writeCopyBuffer("45"); - try osc.writeCopyBuffer("6"); - try osc.writeCopyBuffer("78"); - try osc.writeCopyBuffer("90fin"); - try osc.encodeRemaining(); - try std.testing.expectEqualSlices(u8, "cGFydDFwYXJ0MjEyMzQ1Njc4OTBmaW4=", osc.base64buffer.items); -} - -test "big buffers base64 encode" { - var osc = try OSC52.init(std.testing.allocator, std.testing.io, 1024); - defer osc.free(); - try osc.writeCopyBuffer("part1part1part1part1part1part1part1part1part1|"); - try osc.writeCopyBuffer("part2part2part2part2part2part2part2part2part2"); - try osc.encodeRemaining(); - try std.testing.expectEqualSlices(u8, "cGFydDFwYXJ0MXBhcnQxcGFydDFwYXJ0MXBhcnQxcGFydDFwYXJ0MXBhcnQxfHBhcnQycGFydDJwYXJ0MnBhcnQycGFydDJwYXJ0MnBhcnQycGFydDJwYXJ0Mg==", osc.base64buffer.items); -} diff --git a/src/main.zig b/src/main.zig index ea234e5..5c1a339 100644 --- a/src/main.zig +++ b/src/main.zig @@ -11,16 +11,11 @@ pub fn main(init: std.process.Init) !void { std.process.exit(1); } + std.debug.print("\x1b[0;32mCopying the following to Pasteboard\x1b[0m\n", .{}); + // We want to avoid doing as many allocations as possible so having raw // pages with it being larger should be totally fine var clip = try Clip.OSC52.init(std.heap.page_allocator, init.io, std.heap.pageSize()); - defer clip.free(); - - run(io, clip); -} - -pub fn run(io: *std.Io, clip: anytype) !void { - std.debug.print("\x1b[0;32mCopying the following to Pasteboard\x1b[0m\n", .{}); var buf: [4096]u8 = undefined; while (true) { @@ -43,7 +38,3 @@ pub fn run(io: *std.Io, clip: anytype) !void { std.debug.print("\x1b[0;32mCopied To Pasteboard!\x1b[0m\n", .{}); } - -test { - _ = @import("Clip.zig"); -}