commit 35da86bc7a51572e5aadf0c487b8162752a2b58c
parent efbc80c6bd270370108e9f918e82b3fef8421ebf
Author: mtmn <miro@haravara.org>
Date: Thu, 7 May 2026 22:34:15 +0200
feat: rewrite to zig 0.16
Diffstat:
11 files changed, 237 insertions(+), 249 deletions(-)
diff --git a/pmn/build.zig.zon b/pmn/build.zig.zon
@@ -2,7 +2,7 @@
.name = .pmn,
.version = "0.1.0",
.fingerprint = 0xd1e9e8f723c1d143,
- .minimum_zig_version = "0.15.2",
+ .minimum_zig_version = "0.16.0",
.dependencies = .{},
.paths = .{
"build.zig",
diff --git a/pmn/src/main.zig b/pmn/src/main.zig
@@ -6,21 +6,19 @@ const Entry = struct {
accessed: i128,
};
-pub fn main() !void {
- var gpa = std.heap.GeneralPurposeAllocator(.{}){};
- defer _ = gpa.deinit();
- const alloc = gpa.allocator();
+pub fn main(init: std.process.Init) !void {
+ const alloc = init.gpa;
- const pass_dir = try dir(alloc);
+ const pass_dir = try dir(alloc, init.environ_map);
defer alloc.free(pass_dir);
- var passwords = std.ArrayListUnmanaged(Entry){};
+ var passwords = std.ArrayListUnmanaged(Entry).empty;
defer {
for (passwords.items) |p| alloc.free(p.path);
passwords.deinit(alloc);
}
- try collect(alloc, pass_dir, "", &passwords);
+ try collect(alloc, init.io, pass_dir, "", &passwords);
std.mem.sort(Entry, passwords.items, {}, struct {
fn lessThan(_: void, a: Entry, b: Entry) bool {
@@ -28,13 +26,13 @@ pub fn main() !void {
}
}.lessThan);
- var paths = std.ArrayListUnmanaged([]const u8){};
+ var paths = std.ArrayListUnmanaged([]const u8).empty;
defer paths.deinit(alloc);
for (passwords.items) |entry| {
try paths.append(alloc, entry.path);
}
- const selected = try fzf(alloc, paths.items) orelse return;
+ const selected = try fzf(init, alloc, paths.items) orelse return;
defer alloc.free(selected);
const exists = blk: {
@@ -44,37 +42,37 @@ pub fn main() !void {
break :blk false;
};
const basename_start = if (std.mem.lastIndexOfScalar(u8, selected, '/')) |idx| idx + 1 else 0;
- const has_otp = std.mem.indexOf(u8, selected[basename_start..], "otp") != null;
+ const has_otp = std.mem.find(u8, selected[basename_start..], "otp") != null;
const secret = if (has_otp and exists)
- try run(alloc, &.{ "pass", "otp", "code", selected })
+ try run(init, alloc, &.{ "pass", "otp", "code", selected })
else if (!exists)
- try store(alloc, selected)
+ try store(init, alloc, selected)
else
- run(alloc, &.{ "pass", "show", selected }) catch try store(alloc, selected);
+ run(init, alloc, &.{ "pass", "show", selected }) catch try store(init, alloc, selected);
- try clip(alloc, secret);
+ try clip(init, secret);
alloc.free(secret);
}
-fn dir(alloc: std.mem.Allocator) ![]const u8 {
- if (std.process.getEnvVarOwned(alloc, "PASSWORD_STORE_DIR")) |p| {
- return p;
- } else |_| {}
+fn dir(alloc: std.mem.Allocator, environ: *std.process.Environ.Map) ![]const u8 {
+ if (environ.get("PASSWORD_STORE_DIR")) |p| {
+ return try alloc.dupe(u8, p);
+ }
- const home = std.posix.getenv("HOME") orelse return error.NoHome;
+ const home = environ.get("HOME") orelse return error.NoHome;
return try std.fs.path.join(alloc, &.{ home, ".password-store" });
}
-fn collect(alloc: std.mem.Allocator, base_dir: []const u8, sub_path: []const u8, list: *std.ArrayListUnmanaged(Entry)) anyerror!void {
+fn collect(alloc: std.mem.Allocator, io: std.Io, base_dir: []const u8, sub_path: []const u8, list: *std.ArrayListUnmanaged(Entry)) anyerror!void {
const full_path = try std.fs.path.join(alloc, &.{ base_dir, sub_path });
defer alloc.free(full_path);
- var d = std.fs.openDirAbsolute(full_path, .{ .iterate = true }) catch return;
- defer d.close();
+ var d = std.Io.Dir.openDirAbsolute(io, full_path, .{ .iterate = true }) catch return;
+ defer d.close(io);
var it = d.iterate();
- while (try it.next()) |entry| {
+ while (try it.next(io)) |entry| {
if (entry.name.len > 0 and entry.name[0] == '.') continue;
const entry_sub_path = if (sub_path.len == 0)
@@ -84,7 +82,7 @@ fn collect(alloc: std.mem.Allocator, base_dir: []const u8, sub_path: []const u8,
errdefer alloc.free(entry_sub_path);
if (entry.kind == .directory) {
- try collect(alloc, base_dir, entry_sub_path, list);
+ try collect(alloc, io, base_dir, entry_sub_path, list);
alloc.free(entry_sub_path);
} else if (entry.kind == .file and std.mem.endsWith(u8, entry.name, ".gpg")) {
const name = entry_sub_path[0 .. entry_sub_path.len - 4];
@@ -92,18 +90,19 @@ fn collect(alloc: std.mem.Allocator, base_dir: []const u8, sub_path: []const u8,
defer alloc.free(file_path);
const accessed = blk: {
- const stat = std.fs.openFileAbsolute(file_path, .{}) catch |err| {
+ const file = std.Io.Dir.openFileAbsolute(io, file_path, .{}) catch |err| {
std.debug.print("Failed to stat {s}: {}\n", .{ file_path, err });
break :blk @as(i128, 0);
};
- defer stat.close();
+ defer file.close(io);
- const file_stat = stat.stat() catch |err| {
+ const file_stat = file.stat(io) catch |err| {
std.debug.print("Failed to stat {s}: {}\n", .{ file_path, err });
break :blk @as(i128, 0);
};
- break :blk @as(i128, @intCast(file_stat.atime));
+ const atime_ts = file_stat.atime orelse break :blk @as(i128, 0);
+ break :blk @as(i128, @intCast(atime_ts.nanoseconds));
};
try list.append(alloc, Entry{
@@ -117,33 +116,36 @@ fn collect(alloc: std.mem.Allocator, base_dir: []const u8, sub_path: []const u8,
}
}
-fn fzf(alloc: std.mem.Allocator, items: []const []const u8) !?[]u8 {
- var child = std.process.Child.init(&.{ "fzf", "--print-query" }, alloc);
- child.stdin_behavior = .Pipe;
- child.stdout_behavior = .Pipe;
- child.stderr_behavior = .Inherit;
-
- try child.spawn();
+fn fzf(init: std.process.Init, alloc: std.mem.Allocator, items: []const []const u8) !?[]u8 {
+ var child = try std.process.spawn(init.io, .{
+ .argv = &.{ "fzf", "--print-query" },
+ .stdin = .pipe,
+ .stdout = .pipe,
+ .stderr = .inherit,
+ });
if (child.stdin) |stdin| {
+ var buf: [4096]u8 = undefined;
+ var fw = stdin.writer(init.io, &buf);
for (items) |item| {
- try stdin.writeAll(item);
- try stdin.writeAll("\n");
+ try fw.interface.writeAll(item);
+ try fw.interface.writeAll("\n");
}
- stdin.close();
+ try fw.flush();
+ stdin.close(init.io);
child.stdin = null;
}
- const output = try read(alloc, &child);
+ const output = try read(init.io, alloc, &child);
defer alloc.free(output);
- const term = try child.wait();
- if (term != .Exited) return null;
+ const term = try child.wait(init.io);
+ if (term != .exited) return null;
const trimmed = std.mem.trim(u8, output, " \n\r\t");
if (trimmed.len == 0) return null;
- if (std.mem.indexOfScalar(u8, trimmed, '\n')) |idx| {
+ if (std.mem.findScalar(u8, trimmed, '\n')) |idx| {
const selected = std.mem.trim(u8, trimmed[idx + 1 ..], " \n\r\t");
if (selected.len > 0) return try alloc.dupe(u8, selected);
const query = std.mem.trim(u8, trimmed[0..idx], " \n\r\t");
@@ -153,22 +155,22 @@ fn fzf(alloc: std.mem.Allocator, items: []const []const u8) !?[]u8 {
return try alloc.dupe(u8, trimmed);
}
-fn run(alloc: std.mem.Allocator, args: []const []const u8) ![]u8 {
- var child = std.process.Child.init(args, alloc);
- child.stdout_behavior = .Pipe;
- child.stderr_behavior = .Inherit;
+fn run(init: std.process.Init, alloc: std.mem.Allocator, args: []const []const u8) ![]u8 {
+ var child = try std.process.spawn(init.io, .{
+ .argv = args,
+ .stdout = .pipe,
+ .stderr = .inherit,
+ });
- try child.spawn();
+ const output = try read(init.io, alloc, &child);
- const output = try read(alloc, &child);
-
- const term = try child.wait();
- if (term != .Exited or term.Exited != 0) {
+ const term = try child.wait(init.io);
+ if (term != .exited or term.exited != 0) {
alloc.free(output);
return error.FailedExit;
}
- if (std.mem.indexOfScalar(u8, output, '\n')) |idx| {
+ if (std.mem.findScalar(u8, output, '\n')) |idx| {
const result = try alloc.dupe(u8, output[0..idx]);
alloc.free(output);
return result;
@@ -177,105 +179,106 @@ fn run(alloc: std.mem.Allocator, args: []const []const u8) ![]u8 {
return output;
}
-fn read(alloc: std.mem.Allocator, child: *std.process.Child) ![]u8 {
- var stdout = std.ArrayListUnmanaged(u8){};
- defer stdout.deinit(alloc);
-
- if (child.stdout) |reader| {
- var buffer: [1024]u8 = undefined;
- while (true) {
- const n = try reader.read(&buffer);
- if (n == 0) break;
- try stdout.appendSlice(alloc, buffer[0..n]);
- }
+fn read(io: std.Io, alloc: std.mem.Allocator, child: *std.process.Child) ![]u8 {
+ if (child.stdout) |file| {
+ var buf: [4096]u8 = undefined;
+ var fr = file.reader(io, &buf);
+ return try fr.interface.allocRemaining(alloc, .unlimited);
}
-
- return try alloc.dupe(u8, stdout.items);
+ return try alloc.dupe(u8, "");
}
-fn gen(alloc: std.mem.Allocator) ![]u8 {
+fn gen(init: std.process.Init, alloc: std.mem.Allocator) ![]u8 {
const charset = std.ascii.letters ++ "0123456789";
const special_chars = "!@#$%^&*()-_=+[]{}|;:,.<>?";
var password = try alloc.alloc(u8, 16);
errdefer alloc.free(password);
- std.crypto.random.bytes(password);
+ init.io.random(password);
for (password) |*c| {
c.* = charset[c.* % charset.len];
}
- const pos = std.crypto.random.intRangeLessThan(usize, 0, password.len);
- const idx = std.crypto.random.intRangeLessThan(usize, 0, special_chars.len);
+ var idx_bytes: [8]u8 = undefined;
+ init.io.random(&idx_bytes);
+ const pos = std.mem.readInt(u64, idx_bytes[0..8], .little) % password.len;
+ init.io.random(&idx_bytes);
+ const idx = std.mem.readInt(u64, idx_bytes[0..8], .little) % special_chars.len;
password[pos] = special_chars[idx];
return password;
}
-fn store(alloc: std.mem.Allocator, name: []const u8) ![]u8 {
- const password = try gen(alloc);
+fn store(init: std.process.Init, alloc: std.mem.Allocator, name: []const u8) ![]u8 {
+ const password = try gen(init, alloc);
errdefer alloc.free(password);
- inner(alloc, name, password) catch |err| {
+ inner(init, alloc, name, password) catch |err| {
std.debug.print("Failed to store password: {}\n", .{err});
return err;
};
return password;
}
-fn inner(alloc: std.mem.Allocator, name: []const u8, password: []const u8) !void {
- const pass_dir = try dir(alloc);
+fn inner(init: std.process.Init, alloc: std.mem.Allocator, name: []const u8, password: []const u8) !void {
+ const pass_dir = try dir(alloc, init.environ_map);
defer alloc.free(pass_dir);
const full_path = try std.fs.path.join(alloc, &.{ pass_dir, name });
defer alloc.free(full_path);
if (std.fs.path.dirname(full_path)) |dir_path| {
- std.fs.makeDirAbsolute(dir_path) catch |err| {
+ std.Io.Dir.createDirAbsolute(init.io, dir_path, .default_dir) catch |err| {
if (err != error.PathAlreadyExists) return err;
};
}
- var child = std.process.Child.init(&.{ "pass", "insert", "-e", name }, alloc);
- child.stdin_behavior = .Pipe;
- child.stderr_behavior = .Inherit;
-
- try child.spawn();
+ var child = try std.process.spawn(init.io, .{
+ .argv = &.{ "pass", "insert", "-e", name },
+ .stdin = .pipe,
+ .stderr = .inherit,
+ });
if (child.stdin) |stdin| {
- try stdin.writeAll(password);
- try stdin.writeAll("\n");
- stdin.close();
+ var buf: [4096]u8 = undefined;
+ var fw = stdin.writer(init.io, &buf);
+ try fw.interface.writeAll(password);
+ try fw.interface.writeAll("\n");
+ try fw.flush();
+ stdin.close(init.io);
child.stdin = null;
}
- const term = try child.wait();
- if (term != .Exited or term.Exited != 0) return error.FailedExit;
+ const term = try child.wait(init.io);
+ if (term != .exited or term.exited != 0) return error.FailedExit;
}
-fn clip(alloc: std.mem.Allocator, content: []const u8) !void {
+fn clip(init: std.process.Init, content: []const u8) !void {
const args: []const []const u8 = switch (builtin.os.tag) {
.macos => &.{"pbcopy"},
.linux => args: {
- if (std.process.getEnvVarOwned(alloc, "WAYLAND_DISPLAY")) |v| {
- alloc.free(v);
+ if (init.environ_map.get("WAYLAND_DISPLAY")) |_| {
break :args &.{"wl-copy"};
- } else |_| break :args &.{ "xclip", "-selection", "clipboard" };
+ } else break :args &.{ "xclip", "-selection", "clipboard" };
},
else => return,
};
- var child = std.process.Child.init(args, alloc);
- child.stdin_behavior = .Pipe;
-
- try child.spawn();
+ var child = try std.process.spawn(init.io, .{
+ .argv = args,
+ .stdin = .pipe,
+ });
if (child.stdin) |stdin| {
- try stdin.writeAll(content);
- stdin.close();
+ var buf: [4096]u8 = undefined;
+ var fw = stdin.writer(init.io, &buf);
+ try fw.interface.writeAll(content);
+ try fw.flush();
+ stdin.close(init.io);
child.stdin = null;
}
- const term = try child.wait();
- if (term != .Exited) return error.ClipboardFailed;
+ const term = try child.wait(init.io);
+ if (term != .exited) return error.ClipboardFailed;
}
diff --git a/pracomer/build.zig.zon b/pracomer/build.zig.zon
@@ -28,7 +28,7 @@
// Tracks the earliest Zig version that the package considers to be a
// supported use case.
- .minimum_zig_version = "0.15.2",
+ .minimum_zig_version = "0.16.0",
// This field is optional.
// Each dependency must either provide a `url` and `hash`, or a `path`.
diff --git a/pracomer/src/command.zig b/pracomer/src/command.zig
@@ -8,15 +8,13 @@ pub const Command = enum {
Reset,
};
-const FdType = enum { stdin, signals };
-
pub const Poller = struct {
- poller: std.io.Poller(FdType),
- signals_file: std.fs.File,
- /// Option set to the terminal, is null when running in non-interactive mode
+ stdin_fd: std.posix.fd_t,
+ signals_fd: std.posix.fd_t,
terminal: ?terminal.Terminal,
pub fn init(allocator: std.mem.Allocator) !Poller {
+ _ = allocator;
const opt_terminal = blk: {
break :blk terminal.Terminal.init() catch |err| {
std.log.debug("failed to init terminal: {any}", .{err});
@@ -30,19 +28,12 @@ pub const Poller = struct {
}
const signals_file = try signals.open();
- errdefer signals_file.close();
+ const signals_fd = signals_file.handle;
return Poller{
.terminal = opt_terminal,
- .signals_file = signals_file,
- .poller = std.io.poll(
- allocator,
- FdType,
- .{
- .stdin = std.fs.File.stdin(),
- .signals = signals_file,
- },
- ),
+ .stdin_fd = std.Io.File.stdin().handle,
+ .signals_fd = signals_fd,
};
}
@@ -51,28 +42,34 @@ pub const Poller = struct {
}
pub fn deinit(self: *Poller) void {
- self.poller.deinit();
if (self.terminal) |terminal_| terminal_.deinit();
- self.signals_file.close();
}
pub fn pollTimeout(self: *Poller, nanoseconds: u64) !?Command {
- if (!try self.poller.pollTimeout(nanoseconds)) {
- return null;
- }
+ var fds = [2]std.posix.pollfd{
+ .{ .fd = self.stdin_fd, .events = std.posix.POLL.IN, .revents = 0 },
+ .{ .fd = self.signals_fd, .events = std.posix.POLL.IN, .revents = 0 },
+ };
+
+ const timeout_ms = @as(i32, @intCast(@divTrunc(nanoseconds, std.time.ns_per_ms)));
+ const n = try std.posix.poll(&fds, timeout_ms);
+ if (n == 0) return null;
- if (self.poller.reader(.stdin).buffered().len > 0) {
- const char = try self.poller.reader(.stdin).takeByte();
- switch (char) {
- 'p' => return Command.Pause,
- 'q' => return Command.Quit,
- 'r' => return Command.Reset,
- else => {},
+ if (fds[0].revents & std.posix.POLL.IN != 0) {
+ var buf: [1]u8 = undefined;
+ const bytes_read = try std.posix.read(self.stdin_fd, &buf);
+ if (bytes_read > 0) {
+ switch (buf[0]) {
+ 'p' => return Command.Pause,
+ 'q' => return Command.Quit,
+ 'r' => return Command.Reset,
+ else => {},
+ }
}
}
- if (self.poller.reader(.signals).buffered().len > 0) {
- const signo = try signals.readSignal(self.poller.reader(.signals));
+ if (fds[1].revents & std.posix.POLL.IN != 0) {
+ const signo = try signals.readSignal(self.signals_fd);
switch (signo) {
std.posix.SIG.USR1 => return Command.Pause,
std.posix.SIG.USR2 => return Command.Reset,
diff --git a/pracomer/src/main.zig b/pracomer/src/main.zig
@@ -5,23 +5,19 @@ const std = @import("std");
const builtin = @import("builtin");
const step = @import("step.zig");
-pub fn main() !void {
- var gpa = std.heap.GeneralPurposeAllocator(.{}){};
+pub fn main(init: std.process.Init) !void {
+ const allocator = init.gpa;
- const allocator = gpa.allocator();
- defer std.debug.assert(gpa.deinit() == .ok);
-
- const program_and_args = try std.process.argsAlloc(allocator);
- defer std.process.argsFree(allocator, program_and_args);
+ const program_and_args = try init.minimal.args.toSlice(init.arena.allocator());
const program = program_and_args[0];
- const args = program_and_args[1..];
+ const program_args = program_and_args[1..];
var stdout_buffer: [1024]u8 = undefined;
- var stdout_writer = std.fs.File.stdout().writer(&stdout_buffer);
+ var stdout_writer = std.Io.File.stdout().writer(init.io, &stdout_buffer);
const stdout = &stdout_writer.interface;
- const opts = options.parseArgs(args) catch {
+ const opts = options.parseArgs(program_args) catch {
try printUsage(allocator, stdout, program);
std.process.exit(1);
};
@@ -31,22 +27,21 @@ pub fn main() !void {
try printUsage(allocator, stdout, program);
},
.settings => |settings| {
- try run(allocator, stdout, settings);
+ try run(init, allocator, stdout, settings);
},
}
}
-fn run(allocator: std.mem.Allocator, out: *std.Io.Writer, settings: options.Settings) !void {
+fn run(init: std.process.Init, allocator: std.mem.Allocator, out: *std.Io.Writer, settings: options.Settings) !void {
var poller = try command.Poller.init(allocator);
defer poller.deinit();
- const status_file: ?std.fs.File = if (settings.file) |path| try std.fs.cwd().createFile(path, .{}) else null;
- defer if (status_file) |f| f.close();
- defer if (settings.file) |path| std.fs.cwd().deleteFile(path) catch |err| {
+ const status_file: ?std.Io.File = if (settings.file) |path| try std.Io.Dir.cwd().createFile(init.io, path, .{}) else null;
+ defer if (status_file) |f| f.close(init.io);
+ defer if (settings.file) |path| std.Io.Dir.cwd().deleteFile(init.io, path) catch |err| {
std.log.debug("failed to delete status file: {any}", .{err});
};
- // Print a placeholder to be cleared by the first status
if (poller.interactive()) {
try out.print("\n", .{});
try out.flush();
@@ -54,14 +49,13 @@ fn run(allocator: std.mem.Allocator, out: *std.Io.Writer, settings: options.Sett
var current = step.Step{};
while (true) {
- var timer = try pausable_timer.PausableTimer.init();
+ var timer = pausable_timer.PausableTimer.init(init.io);
var remaining = current.length(settings);
while (remaining > 0) : ({
- remaining = current.length(settings) -| timer.read();
+ remaining = current.length(settings) -| timer.read(init.io);
}) {
- // Clear the last status
if (poller.interactive()) {
try out.print("\x1b[F\x1b[2K\r", .{});
try out.flush();
@@ -70,6 +64,7 @@ fn run(allocator: std.mem.Allocator, out: *std.Io.Writer, settings: options.Sett
allocator,
out,
status_file,
+ init.io,
current,
remaining,
);
@@ -82,15 +77,14 @@ fn run(allocator: std.mem.Allocator, out: *std.Io.Writer, settings: options.Sett
return;
},
command.Command.Pause => {
- try timer.togglePause();
+ timer.togglePause(init.io);
},
command.Command.Reset => {
- // if the step is not started yet
if (timer.paused and remaining == current.length(settings)) {
current = step.Step{};
}
- try timer.reset();
+ timer.reset(init.io);
},
}
}
@@ -98,26 +92,28 @@ fn run(allocator: std.mem.Allocator, out: *std.Io.Writer, settings: options.Sett
current = current.next();
if (settings.notifications and current.length(settings) > 0) {
- notify(allocator, current.step_type.message());
+ notify(init.io, allocator, current.step_type.message());
}
}
}
-fn notify(allocator: std.mem.Allocator, message: []const u8) void {
+fn notify(io: std.Io, allocator: std.mem.Allocator, message: []const u8) void {
if (builtin.os.tag == .macos) {
const script = std.fmt.allocPrint(allocator, "display notification \"{s}\" with title \"pracomer\"", .{message}) catch return;
defer allocator.free(script);
const argv = [_][]const u8{ "osascript", "-e", script };
- var proc = std.process.Child.init(&argv, allocator);
- _ = proc.spawn() catch return;
- _ = proc.wait() catch |err| {
+ var proc = std.process.spawn(io, .{
+ .argv = &argv,
+ }) catch return;
+ _ = proc.wait(io) catch |err| {
std.log.debug("failed to wait for process: {any}", .{err});
};
} else {
const argv = [_][]const u8{ "notify-send", "-u", "critical", message };
- var proc = std.process.Child.init(&argv, allocator);
- _ = proc.spawn() catch return;
- _ = proc.wait() catch |err| {
+ var proc = std.process.spawn(io, .{
+ .argv = &argv,
+ }) catch return;
+ _ = proc.wait(io) catch |err| {
std.log.debug("failed to wait for process: {any}", .{err});
};
}
@@ -126,7 +122,8 @@ fn notify(allocator: std.mem.Allocator, message: []const u8) void {
fn printStatus(
allocator: std.mem.Allocator,
out: *std.Io.Writer,
- status_file: ?std.fs.File,
+ status_file: ?std.Io.File,
+ io: std.Io,
current: step.Step,
remaining: u64,
) !void {
@@ -137,10 +134,12 @@ fn printStatus(
try out.flush();
if (status_file) |f| {
- try f.seekTo(0);
- try f.writeAll(msg);
- try f.writeAll("\n");
- try f.sync();
+ var buf: [1024]u8 = undefined;
+ var fw = f.writer(io, &buf);
+ try fw.seekTo(0);
+ try fw.interface.writeAll(msg);
+ try fw.interface.writeAll("\n");
+ try fw.flush();
}
}
diff --git a/pracomer/src/options.zig b/pracomer/src/options.zig
@@ -8,13 +8,9 @@ pub const Options = union(OptionsTag) {
};
pub const Settings = struct {
- /// Length of a task in minutes
task_length: u32 = 26,
- /// Length of a break in minutes
task_break: u32 = 6,
- /// Enable notifications
notifications: bool = true,
- /// Write output to a file
file: ?[]const u8 = null,
};
@@ -24,7 +20,6 @@ pub fn parseArgs(args: []const []const u8) !Options {
var i: u32 = 0;
while (i < args.len) : (i += 1) {
const arg = args[i];
- // settings options
if (i + 1 < args.len and std.mem.eql(u8, arg, "-t")) {
i += 1;
settings.task_length = try std.fmt.parseInt(u32, args[i], 10);
@@ -36,13 +31,9 @@ pub fn parseArgs(args: []const []const u8) !Options {
} else if (i + 1 < args.len and std.mem.eql(u8, arg, "--file")) {
i += 1;
settings.file = args[i];
- }
- // help
- else if (std.mem.eql(u8, arg, "-h")) {
+ } else if (std.mem.eql(u8, arg, "-h")) {
return .{ .help = void{} };
- }
- // error
- else {
+ } else {
return error.InvalidArgument;
}
}
diff --git a/pracomer/src/pausable_timer.zig b/pracomer/src/pausable_timer.zig
@@ -1,43 +1,41 @@
const std = @import("std");
pub const PausableTimer = struct {
- timer: std.time.Timer,
+ start_time: std.Io.Timestamp,
elapsed_before_pause: u64,
paused: bool,
- pub fn init() !PausableTimer {
- return PausableTimer{
- .timer = try std.time.Timer.start(),
+ pub fn init(io: std.Io) PausableTimer {
+ return .{
+ .start_time = std.Io.Timestamp.now(io, .awake),
.elapsed_before_pause = 0,
.paused = true,
};
}
- pub fn togglePause(self: *PausableTimer) !void {
+ pub fn togglePause(self: *PausableTimer, io: std.Io) void {
if (self.paused) {
- // Resuming
- self.timer.reset();
+ self.start_time = std.Io.Timestamp.now(io, .awake);
self.paused = false;
} else {
- // Pausing
- self.elapsed_before_pause += self.timer.read();
+ const now = std.Io.Timestamp.now(io, .awake);
+ self.elapsed_before_pause += @as(u64, @intCast(self.start_time.durationTo(now).nanoseconds));
self.paused = true;
}
}
- pub fn reset(self: *PausableTimer) !void {
+ pub fn reset(self: *PausableTimer, io: std.Io) void {
self.elapsed_before_pause = 0;
- self.timer.reset();
+ self.start_time = std.Io.Timestamp.now(io, .awake);
self.paused = true;
}
- pub fn read(self: *PausableTimer) u64 {
+ pub fn read(self: *PausableTimer, io: std.Io) u64 {
if (self.paused) {
return self.elapsed_before_pause;
} else {
- // In Zig, timer.read() returns the time since the last reset() or lap()
- // and does NOT update the internal 'previous' field.
- return self.elapsed_before_pause + self.timer.read();
+ const now = std.Io.Timestamp.now(io, .awake);
+ return self.elapsed_before_pause + @as(u64, @intCast(self.start_time.durationTo(now).nanoseconds));
}
}
};
diff --git a/pracomer/src/signals.zig b/pracomer/src/signals.zig
@@ -5,27 +5,26 @@ var signal_pipe: [2]std.posix.fd_t = undefined;
fn signalHandler(sig: i32) callconv(.c) void {
const s: i32 = sig;
- _ = std.posix.write(signal_pipe[1], std.mem.asBytes(&s)) catch |err| {
- std.log.debug("failed to write to signal pipe: {any}", .{err});
- };
+ _ = std.c.write(signal_pipe[1], std.mem.asBytes(&s), @sizeOf(i32));
}
-pub fn open() !std.fs.File {
+pub fn open() !std.Io.File {
if (builtin.os.tag == .linux) {
var mask = std.posix.sigemptyset();
std.posix.sigaddset(&mask, std.posix.SIG.USR1);
std.posix.sigaddset(&mask, std.posix.SIG.USR2);
- // Block signals so they can be handled by signalfd
- _ = std.posix.sigprocmask(std.posix.SIG.BLOCK, &mask, null);
+ std.posix.sigprocmask(std.posix.SIG.BLOCK, &mask, null);
const fd = try std.posix.signalfd(-1, &mask, std.os.linux.SFD.NONBLOCK);
- return .{ .handle = fd };
+ return .{ .handle = fd, .flags = .{ .nonblocking = false } };
} else {
- // macOS / BSD / etc. use self-pipe
- signal_pipe = try std.posix.pipe();
+ const rc = std.c.pipe(&signal_pipe);
+ switch (std.posix.errno(@intCast(rc))) {
+ .SUCCESS => {},
+ else => |err| return std.posix.unexpectedErrno(err),
+ }
- // Set up signal handlers
const sa = std.posix.Sigaction{
.handler = .{ .handler = signalHandler },
.mask = std.posix.sigemptyset(),
@@ -35,17 +34,23 @@ pub fn open() !std.fs.File {
std.posix.sigaction(std.posix.SIG.USR1, &sa, null);
std.posix.sigaction(std.posix.SIG.USR2, &sa, null);
- // Return the read end of the pipe
- return .{ .handle = signal_pipe[0] };
+ return .{ .handle = signal_pipe[0], .flags = .{ .nonblocking = false } };
}
}
-pub fn readSignal(reader: anytype) !i32 {
+pub fn readSignal(fd: std.posix.fd_t) !std.posix.SIG {
if (builtin.os.tag == .linux) {
- const siginfo = try reader.takeStruct(std.os.linux.signalfd_siginfo, .little);
- return @intCast(siginfo.signo);
+ var buf: [@sizeOf(std.os.linux.signalfd_siginfo)]u8 align(@alignOf(std.os.linux.signalfd_siginfo)) = undefined;
+ const n = try std.posix.read(fd, &buf);
+ if (n == 0) return error.Unexpected;
+ const siginfo: *const std.os.linux.signalfd_siginfo = @ptrCast(@alignCast(&buf));
+ return @enumFromInt(siginfo.signo);
} else {
- // Read the signal number from the pipe
- return try reader.takeInt(i32, .little);
+ var buf: [4]u8 = undefined;
+ const n = try std.posix.read(fd, &buf);
+ if (n < 4) return error.Unexpected;
+ var result: i32 = undefined;
+ @memcpy(std.mem.asBytes(&result), &buf);
+ return @enumFromInt(result);
}
}
diff --git a/pracomer/src/terminal.zig b/pracomer/src/terminal.zig
@@ -1,20 +1,12 @@
const std = @import("std");
-/// Turn on these two options on stdin:
-/// - ICANON: disable line buffering
-/// - ECHO: disable echo
-///
-/// Used to be able to read a single character from stdin. Only works when
-/// running in interactive mode
pub const Terminal = struct {
termios: std.posix.termios,
pub fn init() !Terminal {
const old = try std.posix.tcgetattr(std.posix.STDIN_FILENO);
var termios = old;
- // unbuffered input
termios.lflag.ICANON = false;
- // no echo
termios.lflag.ECHO = false;
try std.posix.tcsetattr(std.posix.STDIN_FILENO, .NOW, termios);
return .{ .termios = old };
diff --git a/speediness/build.zig.zon b/speediness/build.zig.zon
@@ -2,7 +2,7 @@
.name = .speediness,
.version = "0.0.0",
.fingerprint = 0x106e839d55011469,
- .minimum_zig_version = "0.15.2",
+ .minimum_zig_version = "0.16.0",
.dependencies = .{},
.paths = .{
"build.zig",
diff --git a/speediness/src/main.zig b/speediness/src/main.zig
@@ -1,5 +1,3 @@
-// speediness: download and upload speed benchmark
-
const std = @import("std");
fn mbps(bytes: usize, ns: u64) f64 {
@@ -8,13 +6,12 @@ fn mbps(bytes: usize, ns: u64) f64 {
return (@as(f64, @floatFromInt(bytes)) / secs) / (1024.0 * 1024.0);
}
-// ── Download ──────────────────────────────────────────────────────────────────
-
-fn downloadTest(allocator: std.mem.Allocator) !void {
+fn downloadTest(io: std.Io, allocator: std.mem.Allocator) !void {
const url = "https://proof.ovh.net/files/100Mb.dat";
var client: std.http.Client = .{
.allocator = allocator,
+ .io = io,
};
defer client.deinit();
@@ -33,20 +30,27 @@ fn downloadTest(allocator: std.mem.Allocator) !void {
return error.HttpError;
}
+ var transfer_buffer: [8192]u8 = undefined;
+ var reader = response.reader(&transfer_buffer);
+
var buf: [131072]u8 = undefined;
var total: usize = 0;
var iter: usize = 0;
- const start = try std.time.Instant.now();
- var reader = response.reader(&.{});
+ const start = std.Io.Timestamp.now(io, .awake);
+ const start_ns: u64 = @intCast(start.nanoseconds);
while (true) {
- const n = reader.readSliceShort(&buf) catch break;
+ const n = reader.readSliceShort(&buf) catch |err| {
+ std.debug.print("\nRead error: {}\n", .{err});
+ break;
+ };
if (n == 0) break;
total += n;
iter += 1;
if (iter % 8 == 0) {
- const now = try std.time.Instant.now();
- const speed = mbps(total, now.since(start));
+ const now_ns: u64 = @intCast(std.Io.Timestamp.now(io, .awake).nanoseconds);
+ const elapsed = now_ns - start_ns;
+ const speed = mbps(total, elapsed);
std.debug.print("\r{d:6.1} MB {d:6.2} MB/s {d:6.1} Mbps ", .{
@as(f64, @floatFromInt(total)) / 1e6,
speed,
@@ -55,22 +59,22 @@ fn downloadTest(allocator: std.mem.Allocator) !void {
}
}
- const end = try std.time.Instant.now();
- const speed = mbps(total, end.since(start));
- const secs = @as(f64, @floatFromInt(end.since(start))) / 1e9;
+ const end_ns: u64 = @intCast(std.Io.Timestamp.now(io, .awake).nanoseconds);
+ const elapsed = end_ns - start_ns;
+ const speed = mbps(total, elapsed);
+ const secs = @as(f64, @floatFromInt(elapsed)) / 1e9;
std.debug.print("\n{d:.1} MB in {d:.2}s => {d:.2} MB/s ({d:.1} Mbps)\n", .{
@as(f64, @floatFromInt(total)) / 1e6, secs, speed, speed * 8,
});
}
-// ── Upload ────────────────────────────────────────────────────────────────────
-
-fn uploadTest(allocator: std.mem.Allocator) !void {
+fn uploadTest(io: std.Io, allocator: std.mem.Allocator) !void {
const url = "https://speed.cloudflare.com/__upload";
- const upload_bytes: usize = 20 * 1024 * 1024; // 20 MB
+ const upload_bytes: usize = 20 * 1024 * 1024;
var client: std.http.Client = .{
.allocator = allocator,
+ .io = io,
};
defer client.deinit();
@@ -86,22 +90,23 @@ fn uploadTest(allocator: std.mem.Allocator) !void {
var body = try req.sendBodyUnflushed(&.{});
- // Stream payload in 128 KB chunks
const chunk_size: usize = 131072;
const chunk = try allocator.alloc(u8, chunk_size);
defer allocator.free(chunk);
@memset(chunk, 0x55);
var sent: usize = 0;
- const start = try std.time.Instant.now();
+ const start = std.Io.Timestamp.now(io, .awake);
+ const start_ns: u64 = @intCast(start.nanoseconds);
while (sent < upload_bytes) {
const to_send = @min(chunk_size, upload_bytes - sent);
try body.writer.writeAll(chunk[0..to_send]);
sent += to_send;
- const now = try std.time.Instant.now();
- const speed = mbps(sent, now.since(start));
+ const now_ns: u64 = @intCast(std.Io.Timestamp.now(io, .awake).nanoseconds);
+ const elapsed = now_ns - start_ns;
+ const speed = mbps(sent, elapsed);
std.debug.print("\r {d:6.1} MB {d:6.2} MB/s {d:6.1} Mbps ", .{
@as(f64, @floatFromInt(sent)) / 1e6,
speed,
@@ -110,33 +115,31 @@ fn uploadTest(allocator: std.mem.Allocator) !void {
}
try body.end();
- try req.connection.?.flush();
+ if (req.connection) |conn| {
+ try conn.flush();
+ }
- // Finish timing after data is flushed
- const end = try std.time.Instant.now();
+ const end_ns: u64 = @intCast(std.Io.Timestamp.now(io, .awake).nanoseconds);
var redirect_buffer: [8192]u8 = undefined;
_ = try req.receiveHead(&redirect_buffer);
- const speed = mbps(sent, end.since(start));
- const secs = @as(f64, @floatFromInt(end.since(start))) / 1e9;
+ const elapsed = end_ns - start_ns;
+ const speed = mbps(sent, elapsed);
+ const secs = @as(f64, @floatFromInt(elapsed)) / 1e9;
std.debug.print("\n{d:.1} MB in {d:.2}s => {d:.2} MB/s ({d:.1} Mbps)\n", .{
@as(f64, @floatFromInt(sent)) / 1e6, secs, speed, speed * 8,
});
}
-// ── Main ──────────────────────────────────────────────────────────────────────
-
-pub fn main() !void {
- var gpa = std.heap.GeneralPurposeAllocator(.{}){};
- defer _ = gpa.deinit();
- const allocator = gpa.allocator();
+pub fn main(init: std.process.Init) !void {
+ const allocator = init.gpa;
- downloadTest(allocator) catch |err| {
+ downloadTest(init.io, allocator) catch |err| {
std.debug.print("Download error: {}\n", .{err});
};
- uploadTest(allocator) catch |err| {
+ uploadTest(init.io, allocator) catch |err| {
std.debug.print("Upload error: {}\n", .{err});
};
}