commit 6d4e9c975b027c7846a7133974c2ad8e8ccb4c00
parent f1e6f51bc60368c7032a44f5d58f451c17179178
Author: mtmn <miro@haravara.org>
Date: Tue, 9 Jun 2026 23:57:51 +0200
refactor: update to 0.16
Diffstat:
11 files changed, 402 insertions(+), 334 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -1,2 +1,3 @@
-/.zig-cache
-/zig-out
+.zig-cache
+zig-pkg
+zig-out
diff --git a/build.zig b/build.zig
@@ -15,12 +15,12 @@ pub fn build(b: *std.Build) void {
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
+ .link_libc = true,
.imports = &.{
.{ .name = "zul", .module = zul.module("zul") },
},
}),
});
- exe.linkLibC();
b.installArtifact(exe);
diff --git a/build.zig.zon b/build.zig.zon
@@ -2,11 +2,11 @@
.name = .magdalena,
.version = "0.1.3",
.fingerprint = 0xd065e535ca3c8b31,
- .minimum_zig_version = "0.15.2",
+ .minimum_zig_version = "0.16.0",
.dependencies = .{
.zul = .{
- .url = "https://github.com/karlseguin/zul/archive/e770047f208cf4538fcd9fc64550c84fd5492fc4.tar.gz",
- .hash = "1220c0fe8bdde860b533f5625b72daaf2c0c686ac7eaf72e123aaf6998d785439b93",
+ .url = "https://github.com/karlseguin/zul/archive/146f9d5b2238c3a621b96345adf03490900c2fe2.tar.gz",
+ .hash = "zul-0.0.0-1oDot2KwBwC9c43wC7V9y4-xxg0a_d9okFDcJPhqmije",
},
},
.paths = .{
diff --git a/justfile b/justfile
@@ -1,5 +1,8 @@
run opt="Debug":
- zig build run -Doptimize={{opt}}
+ zig build run -Doptimize={{ opt }}
build opt="Debug":
- zig build -Doptimize={{opt}} -p build/{{opt}}
+ zig build -Doptimize={{ opt }} -p build/{{ opt }}
+
+install opt="ReleaseSafe" prefix="~/.local":
+ zig build -Doptimize={{ opt }} --prefix {{ prefix }} install
diff --git a/src/cfg.zig b/src/cfg.zig
@@ -1,4 +1,5 @@
const std = @import("std");
+const io = @import("io.zig");
pub const Config = struct {
openers: []Opener = &.{},
@@ -46,14 +47,11 @@ pub fn applyFolderOverride(base: Config, cwd: []const u8) Config {
}
pub fn loadConfig(allocator: std.mem.Allocator) ?std.json.Parsed(Config) {
- const home = std.posix.getenv("HOME") orelse return null;
+ const home = io.getenv("HOME") orelse return null;
const path = std.fs.path.join(allocator, &.{ home, ".config", "magdalena", "config.json" }) catch return null;
defer allocator.free(path);
- const file = std.fs.cwd().openFile(path, .{}) catch return null;
- defer file.close();
-
- const content = file.readToEndAlloc(allocator, std.math.maxInt(usize)) catch return null;
+ const content = std.Io.Dir.cwd().readFileAlloc(io.rt, path, allocator, .unlimited) catch return null;
defer allocator.free(content);
return std.json.parseFromSlice(Config, allocator, content, .{
diff --git a/src/cli.zig b/src/cli.zig
@@ -1,5 +1,4 @@
const std = @import("std");
-const zul = @import("zul");
const io = @import("io.zig");
pub const Action = enum {
@@ -26,15 +25,121 @@ pub const Args = struct {
file_action: ?[]const u8 = null,
depth: ?usize = null,
- _cla: zul.CommandLineArgs,
+ _cla: CommandLineArgs,
pub fn deinit(self: Args) void {
self._cla.deinit();
}
};
-pub fn parseArgs(allocator: std.mem.Allocator) !Args {
- const cla = try zul.CommandLineArgs.parse(allocator);
+// Minimal command-line parser. Replaces zul.CommandLineArgs, whose `parse`
+// still calls the removed `std.process.argsWithAllocator`; the parsing
+// semantics (flag/value/tail handling) are preserved exactly.
+pub const CommandLineArgs = struct {
+ _arena: *std.heap.ArenaAllocator,
+ _lookup: std.StringHashMapUnmanaged([]const u8),
+
+ exe: []const u8,
+ tail: []const [:0]const u8,
+ list: []const [:0]const u8,
+
+ pub fn parse(parent: std.mem.Allocator, source: std.process.Args) !CommandLineArgs {
+ const arena = try parent.create(std.heap.ArenaAllocator);
+ errdefer parent.destroy(arena);
+
+ arena.* = std.heap.ArenaAllocator.init(parent);
+ errdefer arena.deinit();
+
+ const allocator = arena.allocator();
+ const items = try source.toSlice(allocator);
+
+ var lookup: std.StringHashMapUnmanaged([]const u8) = .{};
+
+ if (items.len == 0) {
+ return .{ .exe = "", .tail = &.{}, .list = &.{}, ._arena = arena, ._lookup = lookup };
+ }
+
+ const exe = items[0];
+
+ // 1, skip the exe
+ var i: usize = 1;
+ var tail_start: usize = 1;
+
+ while (i < items.len) {
+ const arg = items[i];
+ if (arg.len == 1 or arg[0] != '-') {
+ // can't be a valid parameter, so it must be the start of our tail
+ break;
+ }
+
+ if (arg[1] == '-') {
+ const kv = KeyValue.from(arg[2..], items, &i);
+ try lookup.put(allocator, kv.key, kv.value);
+ } else {
+ const kv = KeyValue.from(arg[1..], items, &i);
+ const key = kv.key;
+
+ // -xvf file.tar.gz parses into x=>"", v=>"", f=>"file.tar.gz"
+ for (0..key.len - 1) |j| {
+ try lookup.put(allocator, key[j .. j + 1], "");
+ }
+ try lookup.put(allocator, key[key.len - 1 ..], kv.value);
+ }
+ tail_start = i;
+ }
+
+ return .{
+ .exe = exe,
+ .tail = items[tail_start..],
+ .list = items,
+ ._arena = arena,
+ ._lookup = lookup,
+ };
+ }
+
+ pub fn deinit(self: CommandLineArgs) void {
+ const arena = self._arena;
+ const allocator = arena.child_allocator;
+ arena.deinit();
+ allocator.destroy(arena);
+ }
+
+ pub fn contains(self: *const CommandLineArgs, name: []const u8) bool {
+ return self._lookup.contains(name);
+ }
+
+ pub fn get(self: *const CommandLineArgs, name: []const u8) ?[]const u8 {
+ return self._lookup.get(name);
+ }
+};
+
+const KeyValue = struct {
+ key: []const u8,
+ value: []const u8,
+
+ fn from(key: []const u8, items: []const [:0]const u8, i: *usize) KeyValue {
+ const item_index = i.*;
+ if (std.mem.indexOfScalarPos(u8, key, 0, '=')) |pos| {
+ // this parameter is in the form of --key=value, or -k=value
+ i.* = item_index + 1;
+ return .{ .key = key[0..pos], .value = key[pos + 1 ..] };
+ }
+
+ if (item_index == items.len - 1 or items[item_index + 1][0] == '-') {
+ // key is at the end of the arguments OR the next argument starts
+ // with a '-'. This means this key has no value.
+ i.* = item_index + 1;
+ return .{ .key = key, .value = "" };
+ }
+
+ // skip the current key, and the next arg (which is our value)
+ i.* = item_index + 2;
+ return .{ .key = key, .value = items[item_index + 1] };
+ }
+};
+
+pub fn parseArgs(allocator: std.mem.Allocator, source: std.process.Args) !Args {
+ const cla = try CommandLineArgs.parse(allocator, source);
var res = Args{
.action = .help,
diff --git a/src/db.zig b/src/db.zig
@@ -43,20 +43,21 @@ pub const Db = struct {
};
// Ensure directory exists
- std.fs.cwd().makePath(base_path) catch |err| {
+ std.Io.Dir.cwd().createDirPath(io.rt, base_path) catch |err| {
if (err != error.PathAlreadyExists) return err;
};
// Ensure files exist
for (&[_][]const u8{ dirs_path, files_path }) |p| {
- var f = std.fs.cwd().openFile(p, .{ .mode = .read_write }) catch |err| {
+ var f = std.Io.Dir.cwd().openFile(io.rt, p, .{ .mode = .read_write }) catch |err| {
if (err == error.FileNotFound) {
- _ = try std.fs.cwd().createFile(p, .{ .truncate = false });
+ const created = try std.Io.Dir.cwd().createFile(io.rt, p, .{ .truncate = false });
+ created.close(io.rt);
continue;
}
return err;
};
- f.close();
+ f.close(io.rt);
}
return res;
@@ -69,13 +70,10 @@ pub const Db = struct {
}
pub fn recentDirs(self: *Db) !zul.Managed([]DirectoryEntry) {
- const file = std.fs.cwd().openFile(self.dirs_path, .{}) catch |err| {
+ const content = std.Io.Dir.cwd().readFileAlloc(io.rt, self.dirs_path, self.allocator, .limited(64 * 1024 * 1024)) catch |err| {
if (err == error.FileNotFound) return zul.Managed([]DirectoryEntry).fromJson(try std.json.parseFromSlice([]DirectoryEntry, self.allocator, "[]", .{}));
return err;
};
- defer file.close();
-
- const content = try file.readToEndAlloc(self.allocator, 64 * 1024 * 1024);
errdefer self.allocator.free(content);
var arena = try self.allocator.create(std.heap.ArenaAllocator);
@@ -86,7 +84,7 @@ pub const Db = struct {
arena.* = std.heap.ArenaAllocator.init(self.allocator);
const allocator = arena.allocator();
- var list = std.ArrayListUnmanaged(DirectoryEntry){};
+ var list = std.ArrayListUnmanaged(DirectoryEntry).empty;
var seen = std.StringHashMap(void).init(allocator);
@@ -117,13 +115,10 @@ pub const Db = struct {
}
pub fn recentFiles(self: *Db) !zul.Managed([]FileEntry) {
- const file = std.fs.cwd().openFile(self.files_path, .{}) catch |err| {
+ const content = std.Io.Dir.cwd().readFileAlloc(io.rt, self.files_path, self.allocator, .limited(64 * 1024 * 1024)) catch |err| {
if (err == error.FileNotFound) return zul.Managed([]FileEntry).fromJson(try std.json.parseFromSlice([]FileEntry, self.allocator, "[]", .{}));
return err;
};
- defer file.close();
-
- const content = try file.readToEndAlloc(self.allocator, 64 * 1024 * 1024);
errdefer self.allocator.free(content);
var arena = try self.allocator.create(std.heap.ArenaAllocator);
@@ -134,7 +129,7 @@ pub const Db = struct {
arena.* = std.heap.ArenaAllocator.init(self.allocator);
const allocator = arena.allocator();
- var list = std.ArrayListUnmanaged(FileEntry){};
+ var list = std.ArrayListUnmanaged(FileEntry).empty;
var seen = std.StringHashMap(void).init(allocator);
@@ -181,7 +176,7 @@ pub const Db = struct {
arena.* = std.heap.ArenaAllocator.init(self.allocator);
const allocator = arena.allocator();
- var dir_list = std.ArrayListUnmanaged(DirectoryEntry){};
+ var dir_list = std.ArrayListUnmanaged(DirectoryEntry).empty;
for (managed_dirs.value) |d| {
if (std.mem.indexOf(u8, d.path, query_str) != null) {
try dir_list.append(allocator, .{
@@ -191,7 +186,7 @@ pub const Db = struct {
}
}
- var file_list = std.ArrayListUnmanaged(FileEntry){};
+ var file_list = std.ArrayListUnmanaged(FileEntry).empty;
for (managed_files.value) |f| {
if (std.mem.indexOf(u8, f.path, query_str) != null) {
try file_list.append(allocator, .{
@@ -213,7 +208,7 @@ pub const Db = struct {
}
fn resolvePath(self: *Db, path: []const u8) ![]const u8 {
- return std.fs.cwd().realpathAlloc(self.allocator, path) catch {
+ return std.Io.Dir.cwd().realPathFileAlloc(io.rt, path, self.allocator) catch {
return try std.fs.path.resolve(self.allocator, &[_][]const u8{path});
};
}
@@ -222,44 +217,46 @@ pub const Db = struct {
const abs_path = try self.resolvePath(dir_path);
defer self.allocator.free(abs_path);
- const file = std.fs.cwd().openFile(self.dirs_path, .{ .mode = .write_only }) catch |err| switch (err) {
- error.FileNotFound => try std.fs.cwd().createFile(self.dirs_path, .{ .truncate = false }),
+ const file = std.Io.Dir.cwd().openFile(io.rt, self.dirs_path, .{ .mode = .write_only }) catch |err| switch (err) {
+ error.FileNotFound => try std.Io.Dir.cwd().createFile(io.rt, self.dirs_path, .{ .truncate = false }),
else => return err,
};
- defer file.close();
- try file.seekFromEnd(0);
+ defer file.close(io.rt);
- const now = zul.DateTime.now();
+ const now = zul.DateTime.now(io.rt);
var ts_buf: [64]u8 = undefined;
- var ts_writer = std.io.Writer.fixed(&ts_buf);
+ var ts_writer = std.Io.Writer.fixed(&ts_buf);
try now.format(&ts_writer);
const ts = ts_buf[0..ts_writer.end];
- const line = try std.fmt.allocPrint(self.allocator, "{s}|{s}\n", .{ ts, abs_path });
- defer self.allocator.free(line);
- try file.writeAll(line);
+ var fbuf: [4096]u8 = undefined;
+ var fw = file.writer(io.rt, &fbuf);
+ try fw.seekTo(try file.length(io.rt));
+ try fw.interface.print("{s}|{s}\n", .{ ts, abs_path });
+ try fw.interface.flush();
}
pub fn logFile(self: *Db, file_path: []const u8, file_type: []const u8, action: []const u8) !void {
const abs_path = try self.resolvePath(file_path);
defer self.allocator.free(abs_path);
- const file = std.fs.cwd().openFile(self.files_path, .{ .mode = .write_only }) catch |err| switch (err) {
- error.FileNotFound => try std.fs.cwd().createFile(self.files_path, .{ .truncate = false }),
+ const file = std.Io.Dir.cwd().openFile(io.rt, self.files_path, .{ .mode = .write_only }) catch |err| switch (err) {
+ error.FileNotFound => try std.Io.Dir.cwd().createFile(io.rt, self.files_path, .{ .truncate = false }),
else => return err,
};
- defer file.close();
- try file.seekFromEnd(0);
+ defer file.close(io.rt);
- const now = zul.DateTime.now();
+ const now = zul.DateTime.now(io.rt);
var ts_buf: [64]u8 = undefined;
- var ts_writer = std.io.Writer.fixed(&ts_buf);
+ var ts_writer = std.Io.Writer.fixed(&ts_buf);
try now.format(&ts_writer);
const ts = ts_buf[0..ts_writer.end];
- const line = try std.fmt.allocPrint(self.allocator, "{s}|{s}|{s}|{s}\n", .{ ts, file_type, action, abs_path });
- defer self.allocator.free(line);
- try file.writeAll(line);
+ var fbuf: [4096]u8 = undefined;
+ var fw = file.writer(io.rt, &fbuf);
+ try fw.seekTo(try file.length(io.rt));
+ try fw.interface.print("{s}|{s}|{s}|{s}\n", .{ ts, file_type, action, abs_path });
+ try fw.interface.flush();
}
pub fn cleanup(self: *Db) !void {
@@ -270,15 +267,13 @@ pub const Db = struct {
fn cleanupFile(self: *Db, path: []const u8, path_col_idx: usize) !void {
io.warn("Cleaning up {s}...\n", .{path});
- const file = try std.fs.cwd().openFile(path, .{});
- defer file.close();
- const content = try file.readToEndAlloc(self.allocator, 64 * 1024 * 1024);
+ const content = try std.Io.Dir.cwd().readFileAlloc(io.rt, path, self.allocator, .limited(64 * 1024 * 1024));
defer self.allocator.free(content);
var seen = std.StringHashMap(void).init(self.allocator);
defer seen.deinit();
- var lines = std.ArrayListUnmanaged([]const u8){};
+ var lines = std.ArrayListUnmanaged([]const u8).empty;
defer lines.deinit(self.allocator);
var total_entries: usize = 0;
@@ -303,7 +298,7 @@ pub const Db = struct {
if (seen.contains(p)) continue;
- std.fs.cwd().access(p, .{}) catch |err| {
+ std.Io.Dir.cwd().access(io.rt, p, .{}) catch |err| {
if (err == error.FileNotFound) {
missing_entries += 1;
continue;
@@ -318,11 +313,11 @@ pub const Db = struct {
const duplicates = total_entries - lines.items.len - missing_entries;
io.warn(" Entries: {d} total, {d} unique, {d} missing, {d} duplicates removed\n", .{ total_entries, lines.items.len, missing_entries, duplicates });
- const write_file = try std.fs.cwd().createFile(path, .{ .truncate = true });
- defer write_file.close();
+ const write_file = try std.Io.Dir.cwd().createFile(io.rt, path, .{ .truncate = true });
+ defer write_file.close(io.rt);
var write_buf: [4096]u8 = undefined;
- var writer = write_file.writer(&write_buf);
+ var writer = write_file.writer(io.rt, &write_buf);
var i: usize = lines.items.len;
while (i > 0) {
@@ -334,7 +329,7 @@ pub const Db = struct {
};
pub fn getDefaultDbPath(allocator: std.mem.Allocator) ![]const u8 {
- if (std.posix.getenv("HOME")) |home| {
+ if (io.getenv("HOME")) |home| {
return std.fs.path.join(allocator, &.{ home, ".magdalena" });
}
return error.HomeNotFound;
diff --git a/src/fzf.zig b/src/fzf.zig
@@ -26,36 +26,37 @@ fn gotoFile(allocator: std.mem.Allocator, db: ?*hist.Db, file_path: []const u8,
d.logFile(file_path, ext, action) catch |err| io.warn("failed to log file: {}\n", .{err});
}
- if (std.fs.openFileAbsolute("/dev/tty", .{ .mode = .read_write })) |tty| {
- std.posix.dup2(tty.handle, std.posix.STDIN_FILENO) catch |err| io.warn("failed to redirect stdin to tty: {}\n", .{err});
- std.posix.dup2(tty.handle, std.posix.STDOUT_FILENO) catch |err| io.warn("failed to redirect stdout to tty: {}\n", .{err});
- tty.close();
+ if (std.Io.Dir.openFileAbsolute(io.rt, "/dev/tty", .{ .mode = .read_write })) |tty| {
+ if (std.c.dup2(tty.handle, std.posix.STDIN_FILENO) < 0) io.warn("failed to redirect stdin to tty\n", .{});
+ if (std.c.dup2(tty.handle, std.posix.STDOUT_FILENO) < 0) io.warn("failed to redirect stdout to tty\n", .{});
+ tty.close(io.rt);
} else |err| {
io.warn("failed to open /dev/tty: {}\n", .{err});
}
if (command) |cmd| {
- return std.process.execv(allocator, &.{ cmd, file_path });
+ return std.process.replace(io.rt, .{ .argv = &.{ cmd, file_path } });
}
- const editor = config.editor orelse
- std.process.getEnvVarOwned(allocator, "EDITOR") catch |err| switch (err) {
- error.EnvironmentVariableNotFound => return error.EditorNotSet,
- else => return err,
- };
+ const editor = config.editor orelse (io.getenv("EDITOR") orelse return error.EditorNotSet);
if (line) |l| {
const line_arg = try std.fmt.allocPrint(allocator, "+{s}", .{l});
- return std.process.execv(allocator, &.{ editor, line_arg, file_path });
+ return std.process.replace(io.rt, .{ .argv = &.{ editor, line_arg, file_path } });
}
- return std.process.execv(allocator, &.{ editor, file_path });
+ return std.process.replace(io.rt, .{ .argv = &.{ editor, file_path } });
}
+const StdIo = std.process.SpawnOptions.StdIo;
+
const Fzf = struct {
- child: std.process.Child,
+ child: std.process.Child = undefined,
argv: [][]const u8,
allocator: std.mem.Allocator,
+ stdin: StdIo = .pipe,
+ stdout: StdIo = .pipe,
+ stderr: StdIo = .inherit,
pub fn init(allocator: std.mem.Allocator, fzf_opts: []const []const u8, extra_opts: []const []const u8) !Fzf {
var argv = try allocator.alloc([]const u8, 1 + fzf_opts.len + extra_opts.len);
@@ -73,18 +74,21 @@ const Fzf = struct {
i += 1;
}
- var child = std.process.Child.init(argv, allocator);
- child.stdin_behavior = .Pipe;
- child.stdout_behavior = .Pipe;
- child.stderr_behavior = .Inherit;
-
return .{
- .child = child,
.argv = argv,
.allocator = allocator,
};
}
+ pub fn spawn(self: *Fzf) !void {
+ self.child = try std.process.spawn(io.rt, .{
+ .argv = self.argv,
+ .stdin = self.stdin,
+ .stdout = self.stdout,
+ .stderr = self.stderr,
+ });
+ }
+
pub fn deinit(self: *Fzf) void {
self.allocator.free(self.argv);
}
@@ -106,10 +110,10 @@ fn shouldSkip(path: []const u8, ignored_patterns: []const []const u8) bool {
fn runFzfPicker(allocator: std.mem.Allocator, fzf_opts: []const []const u8, items: []const []const u8) ![]u8 {
var fzf = try Fzf.init(allocator, fzf_opts, &[_][]const u8{});
defer fzf.deinit();
+ try fzf.spawn();
const child = &fzf.child;
- try child.spawn();
- if (child.stdin) |*stdin| {
+ if (child.stdin) |stdin| {
// Write items in batches to prevent excessive buffer usage
const batch_size = 1000;
for (0..((items.len + batch_size - 1) / batch_size)) |batch_idx| {
@@ -117,35 +121,32 @@ fn runFzfPicker(allocator: std.mem.Allocator, fzf_opts: []const []const u8, item
const end = @min(start + batch_size, items.len);
for (start..end) |i| {
- stdin.writeAll(items[i]) catch |err| {
+ stdin.writeStreamingAll(io.rt, items[i]) catch |err| {
if (err == error.BrokenPipe) break;
return err;
};
- stdin.writeAll("\n") catch |err| {
+ stdin.writeStreamingAll(io.rt, "\n") catch |err| {
if (err == error.BrokenPipe) break;
return err;
};
}
}
- stdin.close();
+ stdin.close(io.rt);
child.stdin = null;
}
- var stdout_buf = std.ArrayListUnmanaged(u8){};
- defer stdout_buf.deinit(allocator);
+ var stdout_data: ?[]u8 = null;
+ defer if (stdout_data) |d| allocator.free(d);
if (child.stdout) |stdout| {
- var buffer: [1024]u8 = undefined;
- while (true) {
- const bytes_read = try stdout.read(&buffer);
- if (bytes_read == 0) break;
- try stdout_buf.appendSlice(allocator, buffer[0..bytes_read]);
- }
+ var buffer: [4096]u8 = undefined;
+ var sr = stdout.reader(io.rt, &buffer);
+ stdout_data = try sr.interface.allocRemaining(allocator, .unlimited);
}
- const term = try child.wait();
- if (term != .Exited or term.Exited != 0) return error.UserAbort;
- const selected = std.mem.trim(u8, stdout_buf.items, " \n\r\t");
+ const term = try child.wait(io.rt);
+ if (term != .exited or term.exited != 0) return error.UserAbort;
+ const selected = std.mem.trim(u8, stdout_data orelse "", " \n\r\t");
if (selected.len == 0) return error.UserAbort;
return allocator.dupe(u8, selected);
@@ -161,7 +162,7 @@ pub fn recentDir(allocator: std.mem.Allocator, db: *hist.Db, config: *const cfg.
return;
}
- var items = std.ArrayListUnmanaged([]const u8){};
+ var items = std.ArrayListUnmanaged([]const u8).empty;
defer items.deinit(allocator);
for (dirs) |dir| {
@@ -172,7 +173,7 @@ pub fn recentDir(allocator: std.mem.Allocator, db: *hist.Db, config: *const cfg.
const selected = try runFzfPicker(allocator, config.fzf_opts, items.items);
defer allocator.free(selected);
- std.fs.accessAbsolute(selected, .{}) catch |err| {
+ std.Io.Dir.accessAbsolute(io.rt, selected, .{}) catch |err| {
io.warn("Selected directory no longer exists: {s} ({})\n", .{ selected, err });
return error.UserAbort;
};
@@ -189,7 +190,7 @@ pub fn favorites(allocator: std.mem.Allocator, db: *hist.Db, config: *const cfg.
return;
}
- var items = std.ArrayListUnmanaged([]const u8){};
+ var items = std.ArrayListUnmanaged([]const u8).empty;
defer {
for (items.items) |item| allocator.free(item);
items.deinit(allocator);
@@ -204,8 +205,8 @@ pub fn favorites(allocator: std.mem.Allocator, db: *hist.Db, config: *const cfg.
defer allocator.free(selected);
const is_dir = blk: {
- var dir = std.fs.openDirAbsolute(selected, .{}) catch break :blk false;
- dir.close();
+ var dir = std.Io.Dir.openDirAbsolute(io.rt, selected, .{}) catch break :blk false;
+ dir.close(io.rt);
break :blk true;
};
@@ -221,13 +222,13 @@ pub fn favorites(allocator: std.mem.Allocator, db: *hist.Db, config: *const cfg.
}
fn expandPath(allocator: std.mem.Allocator, path: []const u8) ![]const u8 {
- var res = std.ArrayListUnmanaged(u8){};
+ var res = std.ArrayListUnmanaged(u8).empty;
errdefer res.deinit(allocator);
var i: usize = 0;
while (i < path.len) {
if (std.mem.startsWith(u8, path[i..], "$HOME")) {
- const home = std.posix.getenv("HOME") orelse "/home/user";
+ const home = io.getenv("HOME") orelse "/home/user";
try res.appendSlice(allocator, home);
i += 5;
} else {
@@ -249,7 +250,7 @@ pub fn recentFile(allocator: std.mem.Allocator, db: *hist.Db, config: *const cfg
return;
}
- var items = std.ArrayListUnmanaged([]const u8){};
+ var items = std.ArrayListUnmanaged([]const u8).empty;
defer items.deinit(allocator);
for (files) |file| {
@@ -260,7 +261,7 @@ pub fn recentFile(allocator: std.mem.Allocator, db: *hist.Db, config: *const cfg
const selected = try runFzfPicker(allocator, config.fzf_opts, items.items);
defer allocator.free(selected);
- try std.fs.accessAbsolute(selected, .{});
+ try std.Io.Dir.accessAbsolute(io.rt, selected, .{});
try gotoFile(allocator, db, selected, null, config);
}
@@ -268,7 +269,7 @@ pub fn recentFile(allocator: std.mem.Allocator, db: *hist.Db, config: *const cfg
pub fn grep(allocator: std.mem.Allocator, db: *hist.Db, config: *const cfg.Config) !void {
const initial_query = "";
- var rg_cmd = std.ArrayListUnmanaged(u8){};
+ var rg_cmd = std.ArrayListUnmanaged(u8).empty;
defer rg_cmd.deinit(allocator);
try rg_cmd.appendSlice(allocator, "change:reload:rg --column --line-number --no-heading --color=always --smart-case");
for (config.ignored_patterns) |pattern| {
@@ -295,29 +296,26 @@ pub fn grep(allocator: std.mem.Allocator, db: *hist.Db, config: *const cfg.Confi
"right,60%,border-left,+{2}+3/3,~3",
});
defer fzf.deinit();
- const child = &fzf.child;
- child.stdin_behavior = .Inherit;
- child.stdout_behavior = .Pipe;
- child.stderr_behavior = .Inherit;
+ fzf.stdin = .inherit;
+ fzf.stdout = .pipe;
+ fzf.stderr = .inherit;
- try child.spawn();
+ try fzf.spawn();
+ const child = &fzf.child;
- var stdout_buf = std.ArrayListUnmanaged(u8){};
- defer stdout_buf.deinit(allocator);
+ var stdout_data: ?[]u8 = null;
+ defer if (stdout_data) |d| allocator.free(d);
if (child.stdout) |stdout| {
- var buffer: [1024]u8 = undefined;
- while (true) {
- const bytes_read = try stdout.read(&buffer);
- if (bytes_read == 0) break;
- try stdout_buf.appendSlice(allocator, buffer[0..bytes_read]);
- }
+ var buffer: [4096]u8 = undefined;
+ var sr = stdout.reader(io.rt, &buffer);
+ stdout_data = try sr.interface.allocRemaining(allocator, .unlimited);
}
- const term = try child.wait();
- if (term != .Exited or term.Exited != 0) return error.UserAbort;
- const selected = std.mem.trim(u8, stdout_buf.items, " \n\r\t");
+ const term = try child.wait(io.rt);
+ if (term != .exited or term.exited != 0) return error.UserAbort;
+ const selected = std.mem.trim(u8, stdout_data orelse "", " \n\r\t");
if (selected.len == 0) return error.UserAbort;
var it = std.mem.splitScalar(u8, selected, ':');
@@ -340,71 +338,111 @@ const Entry = struct {
mtime: i128,
};
-fn getMaxDirMtime(allocator: std.mem.Allocator, ignored_patterns: []const []const u8, max_depth: usize) !i128 {
+fn getMaxDirMtime(ignored_patterns: []const []const u8, max_depth: usize) !i128 {
var max_mtime: i128 = 0;
- if (std.fs.cwd().statFile(".")) |stat| {
- max_mtime = stat.mtime;
+ if (std.Io.Dir.cwd().statFile(io.rt, ".", .{})) |stat| {
+ max_mtime = stat.mtime.nanoseconds;
} else |_| {}
- const StackEntry = struct {
- dir: std.fs.Dir,
- depth: usize,
- };
- var stack = std.ArrayListUnmanaged(StackEntry){};
- defer {
- for (stack.items) |*se| se.dir.close();
- stack.deinit(allocator);
- }
+ var root = try std.Io.Dir.cwd().openDir(io.rt, ".", .{ .iterate = true });
+ defer root.close(io.rt);
+ try scanMaxMtime(&root, 0, max_depth, ignored_patterns, &max_mtime);
- try stack.append(allocator, .{ .dir = try std.fs.cwd().openDir(".", .{ .iterate = true }), .depth = 0 });
+ return max_mtime;
+}
- while (stack.items.len > 0) {
- var current = stack.pop() orelse unreachable;
- defer current.dir.close();
+// Recurses one open directory per call frame, so each handle is closed exactly
+// once by its own `defer` as the recursion unwinds.
+fn scanMaxMtime(dir: *std.Io.Dir, depth: usize, max_depth: usize, ignored_patterns: []const []const u8, max_mtime: *i128) !void {
+ var it = dir.iterate();
+ while (try it.next(io.rt)) |entry| {
+ var skip = false;
+ for (ignored_patterns) |pattern| {
+ if (std.mem.eql(u8, entry.name, pattern)) {
+ skip = true;
+ break;
+ }
+ }
+ if (skip) continue;
- var it = current.dir.iterate();
- while (try it.next()) |entry| {
- var skip = false;
- for (ignored_patterns) |pattern| {
- if (std.mem.eql(u8, entry.name, pattern)) {
- skip = true;
- break;
+ const is_dir = entry.kind == .directory;
+ if (is_dir or entry.kind == .sym_link) {
+ const stat = dir.statFile(io.rt, entry.name, .{}) catch |err| {
+ if (err != error.FileNotFound and err != error.AccessDenied) {
+ io.warn("failed to stat {s}: {}\n", .{ entry.name, err });
}
- }
- if (skip) continue;
+ continue;
+ };
+ if (stat.kind == .directory) {
+ if (stat.mtime.nanoseconds > max_mtime.*) max_mtime.* = stat.mtime.nanoseconds;
- const is_dir = entry.kind == .directory;
- if (is_dir or entry.kind == .sym_link) {
- const stat = current.dir.statFile(entry.name) catch |err| {
- if (err != error.FileNotFound and err != error.AccessDenied) {
- io.warn("failed to stat {s}: {}\n", .{ entry.name, err });
- }
- continue;
- };
- if (stat.kind == .directory) {
- if (stat.mtime > max_mtime) max_mtime = stat.mtime;
-
- if (is_dir and current.depth + 1 < max_depth) {
- const sub_dir = current.dir.openDir(entry.name, .{ .iterate = true }) catch |err| {
- io.warn("failed to open directory {s}: {}\n", .{ entry.name, err });
- continue;
- };
- try stack.append(allocator, .{ .dir = sub_dir, .depth = current.depth + 1 });
- }
+ if (is_dir and depth + 1 < max_depth) {
+ var sub_dir = dir.openDir(io.rt, entry.name, .{ .iterate = true }) catch |err| {
+ io.warn("failed to open directory {s}: {}\n", .{ entry.name, err });
+ continue;
+ };
+ defer sub_dir.close(io.rt);
+ try scanMaxMtime(&sub_dir, depth + 1, max_depth, ignored_patterns, max_mtime);
}
}
}
}
+}
- return max_mtime;
+// Recurses one open directory per call frame (closed by its own `defer`),
+// collecting matching entries with their path relative to the walk root.
+fn collectEntries(
+ allocator: std.mem.Allocator,
+ dir: *std.Io.Dir,
+ prefix: ?[]const u8,
+ depth: usize,
+ max_depth: usize,
+ kind: std.Io.File.Kind,
+ ignored_patterns: []const []const u8,
+ entries: *std.ArrayListUnmanaged(Entry),
+) !void {
+ var it = dir.iterate();
+ while (try it.next(io.rt)) |entry| {
+ const entry_path = if (prefix) |p|
+ try std.fs.path.join(allocator, &.{ p, entry.name })
+ else
+ try allocator.dupe(u8, entry.name);
+ defer allocator.free(entry_path);
+
+ if (shouldSkip(entry_path, ignored_patterns)) continue;
+
+ if (entry.kind == kind or entry.kind == .sym_link) {
+ if (dir.statFile(io.rt, entry.name, .{})) |stat| {
+ if (stat.kind == kind) {
+ try entries.append(allocator, .{
+ .path = try allocator.dupe(u8, entry_path),
+ .mtime = stat.mtime.nanoseconds,
+ });
+ }
+ } else |err| {
+ if (err != error.FileNotFound and err != error.AccessDenied) {
+ io.warn("failed to stat {s}: {}\n", .{ entry.name, err });
+ }
+ }
+ }
+
+ if (entry.kind == .directory and depth + 1 < max_depth) {
+ var sub_dir = dir.openDir(io.rt, entry.name, .{ .iterate = true }) catch |err| {
+ io.warn("failed to open directory {s}: {}\n", .{ entry.name, err });
+ continue;
+ };
+ defer sub_dir.close(io.rt);
+ try collectEntries(allocator, &sub_dir, entry_path, depth + 1, max_depth, kind, ignored_patterns, entries);
+ }
+ }
}
-fn runFzfWithWalker(allocator: std.mem.Allocator, db: *hist.Db, kind: std.fs.File.Kind, config: *const cfg.Config, depth: ?usize) !void {
+fn runFzfWithWalker(allocator: std.mem.Allocator, db: *hist.Db, kind: std.Io.File.Kind, config: *const cfg.Config, depth: ?usize) !void {
const is_dir = kind == .directory;
const max_depth = depth orelse config.max_depth;
- var entries = std.ArrayListUnmanaged(Entry){};
+ var entries = std.ArrayListUnmanaged(Entry).empty;
defer {
for (entries.items) |e| allocator.free(e.path);
entries.deinit(allocator);
@@ -424,7 +462,7 @@ fn runFzfWithWalker(allocator: std.mem.Allocator, db: *hist.Db, kind: std.fs.Fil
var cache_hit = false;
if (mc) |*m| {
const cwd = blk: {
- break :blk std.process.getCwdAlloc(allocator) catch |err| {
+ break :blk std.process.currentPathAlloc(io.rt, allocator) catch |err| {
io.warn("failed to get cwd for cache: {}\n", .{err});
break :blk null;
};
@@ -433,7 +471,7 @@ fn runFzfWithWalker(allocator: std.mem.Allocator, db: *hist.Db, kind: std.fs.Fil
defer allocator.free(c);
const hash = std.hash.Crc32.hash(c);
- const max_dir_mtime = getMaxDirMtime(allocator, config.ignored_patterns, max_depth) catch blk: {
+ const max_dir_mtime = getMaxDirMtime(config.ignored_patterns, max_depth) catch blk: {
break :blk @as(i128, 0);
};
const mtime = @divTrunc(max_dir_mtime, std.time.ns_per_s);
@@ -457,66 +495,9 @@ fn runFzfWithWalker(allocator: std.mem.Allocator, db: *hist.Db, kind: std.fs.Fil
}
if (!cache_hit) {
- const StackEntry = struct {
- dir: std.fs.Dir,
- path: ?[]const u8,
- depth: usize,
- };
- var stack = std.ArrayListUnmanaged(StackEntry){};
- defer {
- for (stack.items) |*se| se.dir.close();
- stack.deinit(allocator);
- }
-
- try stack.append(allocator, .{ .dir = try std.fs.cwd().openDir(".", .{ .iterate = true }), .path = null, .depth = 0 });
-
- while (stack.items.len > 0) {
- var current = stack.pop() orelse unreachable;
- defer {
- current.dir.close();
- if (current.path) |p| allocator.free(p);
- }
-
- var it = current.dir.iterate();
- while (try it.next()) |entry| {
- const entry_path = if (current.path) |p|
- try std.fs.path.join(allocator, &.{ p, entry.name })
- else
- try allocator.dupe(u8, entry.name);
- errdefer allocator.free(entry_path);
-
- if (shouldSkip(entry_path, config.ignored_patterns)) {
- allocator.free(entry_path);
- continue;
- }
-
- if (entry.kind == kind or entry.kind == .sym_link) {
- if (current.dir.statFile(entry.name)) |stat| {
- if (stat.kind == kind) {
- try entries.append(allocator, .{
- .path = try allocator.dupe(u8, entry_path),
- .mtime = stat.mtime,
- });
- }
- } else |err| {
- if (err != error.FileNotFound and err != error.AccessDenied) {
- io.warn("failed to stat {s}: {}\n", .{ entry.name, err });
- }
- }
- }
-
- if (entry.kind == .directory and current.depth + 1 < max_depth) {
- const sub_dir = current.dir.openDir(entry.name, .{ .iterate = true }) catch |err| {
- io.warn("failed to open directory {s}: {}\n", .{ entry.name, err });
- allocator.free(entry_path);
- continue;
- };
- try stack.append(allocator, .{ .dir = sub_dir, .path = entry_path, .depth = current.depth + 1 });
- } else {
- allocator.free(entry_path);
- }
- }
- }
+ var root = try std.Io.Dir.cwd().openDir(io.rt, ".", .{ .iterate = true });
+ defer root.close(io.rt);
+ try collectEntries(allocator, &root, null, 0, max_depth, kind, config.ignored_patterns, &entries);
if (mc) |*m| {
if (cache_key) |key| {
@@ -533,7 +514,7 @@ fn runFzfWithWalker(allocator: std.mem.Allocator, db: *hist.Db, kind: std.fs.Fil
}
}.lessThan);
- var items = std.ArrayListUnmanaged([]const u8){};
+ var items = std.ArrayListUnmanaged([]const u8).empty;
defer items.deinit(allocator);
for (entries.items) |entry| {
try items.append(allocator, entry.path);
@@ -545,7 +526,7 @@ fn runFzfWithWalker(allocator: std.mem.Allocator, db: *hist.Db, kind: std.fs.Fil
const clean_path = if (std.mem.startsWith(u8, selected, "./")) selected[2..] else selected;
if (clean_path.len > 0) {
- std.fs.cwd().access(clean_path, .{}) catch |err| {
+ std.Io.Dir.cwd().access(io.rt, clean_path, .{}) catch |err| {
io.warn("Selected item no longer exists or is inaccessible: {s} ({})\n", .{ clean_path, err });
return error.UserAbort;
};
diff --git a/src/io.zig b/src/io.zig
@@ -1,7 +1,17 @@
const std = @import("std");
+// Process-wide handles, set once from `std.process.Init` in `main`. The app is
+// a short-lived single-threaded CLI, so threading these through every call site
+// would be pure noise; a couple of globals keep the IO surface small.
+pub var rt: std.Io = undefined;
+pub var env: *std.process.Environ.Map = undefined;
+
+pub fn getenv(key: []const u8) ?[]const u8 {
+ return env.get(key);
+}
+
pub const Writer = struct {
- inner: std.fs.File.Writer,
+ inner: std.Io.File.Writer,
failed: bool = false,
pub fn print(self: *Writer, comptime fmt: []const u8, args: anytype) !void {
@@ -17,7 +27,7 @@ pub const Writer = struct {
pub fn end(self: *Writer) !void {
if (self.failed) return;
- self.inner.end() catch |err| {
+ self.inner.interface.flush() catch |err| {
if (err == error.WriteFailed) {
self.failed = true;
return;
@@ -28,17 +38,17 @@ pub const Writer = struct {
};
pub fn getStdout(buf: []u8) Writer {
- return .{ .inner = std.fs.File.stdout().writer(buf) };
+ return .{ .inner = std.Io.File.stdout().writerStreaming(rt, buf) };
}
pub fn warn(comptime fmt: []const u8, args: anytype) void {
var buf: [4096]u8 = undefined;
- var writer = std.fs.File.stderr().writer(&buf);
+ var writer = std.Io.File.stderr().writerStreaming(rt, &buf);
writer.interface.print(fmt, args) catch |err| {
std.log.warn("failed to write to stderr: {}", .{err});
};
- writer.end() catch |err| {
- std.log.warn("failed to end stderr writer: {}", .{err});
+ writer.interface.flush() catch |err| {
+ std.log.warn("failed to flush stderr writer: {}", .{err});
};
}
diff --git a/src/main.zig b/src/main.zig
@@ -5,12 +5,12 @@ const fzf = @import("fzf.zig");
const hist = @import("db.zig");
const io = @import("io.zig");
-pub fn main() void {
- var gpa = std.heap.GeneralPurposeAllocator(.{}){};
- defer _ = gpa.deinit();
- const allocator = gpa.allocator();
+pub fn main(init: std.process.Init) void {
+ io.rt = init.io;
+ io.env = init.environ_map;
+ const allocator = init.gpa;
- const args = cli.parseArgs(allocator) catch |err| {
+ const args = cli.parseArgs(allocator, init.minimal.args) catch |err| {
io.fatal("failed to parse arguments: {}", .{err});
};
defer args.deinit();
@@ -28,9 +28,12 @@ pub fn main() void {
const base_config = if (parsed_config) |c| c.value else default_config;
var cwd_buf: [std.fs.max_path_bytes]u8 = undefined;
- const cwd = std.process.getCwd(&cwd_buf) catch |err| blk: {
- io.warn("failed to get cwd for folder overrides: {}\n", .{err});
- break :blk "";
+ const cwd = blk: {
+ const n = std.process.currentPath(io.rt, &cwd_buf) catch |err| {
+ io.warn("failed to get cwd for folder overrides: {}\n", .{err});
+ break :blk "";
+ };
+ break :blk cwd_buf[0..n];
};
const config = cfg.applyFolderOverride(base_config, cwd);
diff --git a/src/memcached.zig b/src/memcached.zig
@@ -3,16 +3,16 @@ const io = @import("io.zig");
pub const Memcached = struct {
allocator: std.mem.Allocator,
- stream: std.net.Stream,
- write_buf: [4096]u8 = undefined,
- write_end: usize = 0,
+ stream: std.Io.net.Stream,
read_buf: [65536]u8 = undefined,
- read_start: usize = 0,
- read_end: usize = 0,
+ write_buf: [4096]u8 = undefined,
+ reader: std.Io.net.Stream.Reader = undefined,
+ writer: std.Io.net.Stream.Writer = undefined,
+ started: bool = false,
pub fn init(allocator: std.mem.Allocator, host: []const u8, port: u16) !Memcached {
- const address = try std.net.Address.parseIp4(host, port);
- const stream = try std.net.tcpConnectToAddress(address);
+ const address = try std.Io.net.IpAddress.parseIp4(host, port);
+ const stream = try address.connect(io.rt, .{ .mode = .stream });
return .{
.allocator = allocator,
.stream = stream,
@@ -20,30 +20,40 @@ pub const Memcached = struct {
}
pub fn deinit(self: *Memcached) void {
- self.flushWrite() catch |err| {
- io.warn("failed to flush memcached on deinit: {}\n", .{err});
- };
- self.stream.close();
+ if (self.started) {
+ self.writer.interface.flush() catch |err| {
+ io.warn("failed to flush memcached on deinit: {}\n", .{err});
+ };
+ }
+ self.stream.close(io.rt);
+ }
+
+ // The Stream's buffered Reader/Writer hold pointers into this struct's own
+ // buffers, so they must be initialized only once the struct sits at its
+ // final address (i.e. after `init` returns by value).
+ fn ensureStarted(self: *Memcached) void {
+ if (self.started) return;
+ self.reader = self.stream.reader(io.rt, &self.read_buf);
+ self.writer = self.stream.writer(io.rt, &self.write_buf);
+ self.started = true;
+ }
+
+ fn r(self: *Memcached) *std.Io.Reader {
+ self.ensureStarted();
+ return &self.reader.interface;
+ }
+
+ fn w(self: *Memcached) *std.Io.Writer {
+ self.ensureStarted();
+ return &self.writer.interface;
}
fn flushWrite(self: *Memcached) !void {
- if (self.write_end == 0) return;
- try self.stream.writeAll(self.write_buf[0..self.write_end]);
- self.write_end = 0;
+ try self.w().flush();
}
fn bufferedWrite(self: *Memcached, data: []const u8) !void {
- if (data.len >= self.write_buf.len) {
- try self.flushWrite();
- try self.stream.writeAll(data);
- return;
- }
- const available = self.write_buf.len - self.write_end;
- if (data.len > available) {
- try self.flushWrite();
- }
- @memcpy(self.write_buf[self.write_end..][0..data.len], data);
- self.write_end += data.len;
+ try self.w().writeAll(data);
}
const chunk_size = 900 * 1024;
@@ -82,80 +92,41 @@ pub const Memcached = struct {
}
fn readLine(self: *Memcached, buf: []u8) !?[]const u8 {
- while (true) {
- if (self.read_start < self.read_end) {
- if (std.mem.indexOfScalar(u8, self.read_buf[self.read_start..self.read_end], '\n')) |newline_offset| {
- const line_start = self.read_start;
- const line_end = self.read_start + newline_offset;
- self.read_start = line_end + 1;
- const line = self.read_buf[line_start..line_end];
- const trimmed = std.mem.trim(u8, line, "\r");
- if (buf.len < trimmed.len) return error.BufferTooSmall;
- @memcpy(buf[0..trimmed.len], trimmed);
- return buf[0..trimmed.len];
- }
- }
- if (self.read_start > 0) {
- const remaining = self.read_end - self.read_start;
- @memcpy(self.read_buf[0..remaining], self.read_buf[self.read_start..self.read_end]);
- self.read_end = remaining;
- self.read_start = 0;
- }
- if (self.read_end >= self.read_buf.len) return error.LineTooLong;
- const n = try self.stream.read(self.read_buf[self.read_end..]);
- if (n == 0) return null;
- self.read_end += n;
- }
+ const line = self.r().takeDelimiterExclusive('\n') catch |err| switch (err) {
+ error.EndOfStream => return null,
+ error.StreamTooLong => return error.LineTooLong,
+ else => |e| return e,
+ };
+ const trimmed = std.mem.trim(u8, line, "\r");
+ if (buf.len < trimmed.len) return error.BufferTooSmall;
+ @memcpy(buf[0..trimmed.len], trimmed);
+ return buf[0..trimmed.len];
}
fn readExact(self: *Memcached, buf: []u8) !void {
- var offset: usize = 0;
- while (offset < buf.len) {
- if (self.read_start < self.read_end) {
- const available = self.read_end - self.read_start;
- const needed = buf.len - offset;
- const to_copy = @min(available, needed);
- @memcpy(buf[offset..][0..to_copy], self.read_buf[self.read_start..][0..to_copy]);
- self.read_start += to_copy;
- offset += to_copy;
- continue;
- }
- self.read_start = 0;
- self.read_end = 0;
- const remaining = buf.len - offset;
- if (remaining >= self.read_buf.len) {
- const n = try self.stream.read(buf[offset..]);
- if (n == 0) return error.IncompleteRead;
- offset += n;
- } else {
- const n = try self.stream.read(self.read_buf[0..]);
- if (n == 0) return error.IncompleteRead;
- self.read_end = n;
- }
- }
+ self.r().readSliceAll(buf) catch |err| switch (err) {
+ error.EndOfStream => return error.IncompleteRead,
+ else => |e| return e,
+ };
}
fn consumeCrlf(self: *Memcached) void {
- if (self.read_start < self.read_end) {
- const available = self.read_end - self.read_start;
- if (available >= 2 and self.read_buf[self.read_start] == '\r' and self.read_buf[self.read_start + 1] == '\n') {
- self.read_start += 2;
- return;
- }
- if (available >= 1 and self.read_buf[self.read_start] == '\n') {
- self.read_start += 1;
- return;
- }
+ const reader = self.r();
+ const first = reader.peekByte() catch return;
+ if (first == '\r') {
+ reader.toss(1);
+ const second = reader.peekByte() catch return;
+ if (second == '\n') reader.toss(1);
+ } else if (first == '\n') {
+ reader.toss(1);
}
- var discard: [2]u8 = undefined;
- _ = self.stream.read(&discard) catch return;
}
pub fn get(self: *Memcached, key: []const u8) !?[]u8 {
- try self.flushWrite();
var cmd_buf: [512]u8 = undefined;
const cmd = try std.fmt.bufPrint(&cmd_buf, "mg {s} v\r\n", .{key});
- try self.stream.writeAll(cmd);
+ try self.bufferedWrite(cmd);
+ try self.flushWrite();
var line_buf: [512]u8 = undefined;
const line = (try self.readLine(&line_buf)) orelse return null;
@@ -203,7 +174,8 @@ pub const Memcached = struct {
var cmd_buf: [512]u8 = undefined;
const cmd = try std.fmt.bufPrint(&cmd_buf, "mg {s} v\r\n", .{chunk_key});
- try self.stream.writeAll(cmd);
+ try self.bufferedWrite(cmd);
+ try self.flushWrite();
var line_buf: [512]u8 = undefined;
const line = (try self.readLine(&line_buf)) orelse return error.CacheChunkMissing;